<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vincent Yang</title>
    <description>The latest articles on DEV Community by Vincent Yang (@vincent4486).</description>
    <link>https://dev.to/vincent4486</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3784540%2F2ac14382-f5c1-4972-b009-ba8eff183bbd.png</url>
      <title>DEV Community: Vincent Yang</title>
      <link>https://dev.to/vincent4486</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vincent4486"/>
    <language>en</language>
    <item>
      <title>The Internet is a Thin Cylinder: Supporting Millions, Supported by One</title>
      <dc:creator>Vincent Yang</dc:creator>
      <pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/vincent4486/the-lonely-places-of-the-internet-4pkc</link>
      <guid>https://dev.to/vincent4486/the-lonely-places-of-the-internet-4pkc</guid>
      <description>&lt;p&gt;The internet is not always a busy place full of charm, but that is only&lt;br&gt;
a blink of it.&lt;/p&gt;

&lt;p&gt;The other places? Either no one knew it existed, or the only thing that&lt;br&gt;
knew it was the bots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this an actual thing?
&lt;/h2&gt;

&lt;p&gt;You might have been thinking, how could this happen? As of 2026, there&lt;br&gt;
are at least 1 billion websites, and if all these just have a few links&lt;br&gt;
to another, everyone should be able to see and visit them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eoo2r18ptylwzv4gu2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eoo2r18ptylwzv4gu2z.png" alt="Growth of websites since the WWW started" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this is not the case, the reason is not because people &lt;strong&gt;cannot find&lt;br&gt;
it&lt;/strong&gt;, it is because they &lt;strong&gt;ignored&lt;/strong&gt; it, because the site had been flushed&lt;br&gt;
away by the high-speed development of the Internet.&lt;/p&gt;

&lt;p&gt;Some of the sites and tools might be a legendary in the past. But after&lt;br&gt;
new tools and technology came in, it is quickly forgotten, only leaving&lt;br&gt;
the few people quietly maintaining it.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example of this
&lt;/h2&gt;

&lt;p&gt;There are millions of examples of old websites that have been forgotten&lt;br&gt;
by time. We can take the GNU project Nano as an example that is fairly&lt;br&gt;
neutral, not too active, but not completely dead.&lt;/p&gt;

&lt;p&gt;It is not a dead project; in fact, it is quite active. But active does&lt;br&gt;
not mean it has a big community; few people can create an active&lt;br&gt;
environment via just creating code, but few people cannot create charm&lt;br&gt;
as it requires not just the code.&lt;/p&gt;

&lt;p&gt;If you look at the git log of Nano or the mailing list, you will find&lt;br&gt;
out that the only person that is really contributing to the project a&lt;br&gt;
lot is the maintainer.&lt;/p&gt;

&lt;p&gt;And this project is the text editor that millions of programmers every&lt;br&gt;
day, a very important tool that I also use a lot of in. I am very&lt;br&gt;
thankful that its creator created it, maybe at the time, they never knew&lt;br&gt;
that it's going to be this big.&lt;/p&gt;

&lt;p&gt;I am not saying that this is not good, or anything I mentioned in this&lt;br&gt;
post. Sometimes these projects are just "finished", and there are&lt;br&gt;
seldom bugs; a few people are enough for it to continue. Or working&lt;br&gt;
alone is what people prefer, including me.&lt;/p&gt;

&lt;p&gt;I chose this project as an example, not because it is the worst. There&lt;br&gt;
are thousands of projects that have no one maintaining, it is because it&lt;br&gt;
represents the very fragile state of technology - &lt;strong&gt;&lt;em&gt;Supporting millions&lt;br&gt;
of people, supported by 1 person.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New sites are also involved
&lt;/h2&gt;

&lt;p&gt;Nano is an old project, but not only old projects are lonely, but new&lt;br&gt;
ones also do, in the same way. But it's also, a different way. That&lt;br&gt;
creates loneliness.&lt;/p&gt;

&lt;p&gt;Technology only grabs attention when it can actually do something, or&lt;br&gt;
when it is interesting, projects like Nginx and Chromium give a very&lt;br&gt;
clear use, therefore people contribute to it and create an active&lt;br&gt;
environment.&lt;/p&gt;

&lt;p&gt;But not all provides this type of &lt;strong&gt;engagement&lt;/strong&gt; and &lt;strong&gt;interest&lt;/strong&gt; of&lt;br&gt;
their websites and projects, take an example of my website at&lt;br&gt;
&lt;a href="https://www.vyang.org/" rel="noopener noreferrer"&gt;vyang.org&lt;/a&gt;, this is a simple website about&lt;br&gt;
myself.&lt;/p&gt;

&lt;p&gt;I get about a few hundred unique visitors requesting my site; most of&lt;br&gt;
them are robots or Internet scans for vulnerabilities. Almost 70% of my&lt;br&gt;
requests are made by robots.&lt;/p&gt;

&lt;p&gt;This gives you a fake feeling that your website is visited by a lot of&lt;br&gt;
"people", but the "people" are not actual "people".&lt;/p&gt;

&lt;p&gt;This impacted the creativeness of the Internet, as of most people are&lt;br&gt;
moving into big platforms to post their things, and not like the&lt;br&gt;
Internet before, where we have to go access the entire Internet to find&lt;br&gt;
the solution to a problem.&lt;/p&gt;

&lt;p&gt;Because of this, the Internet will be highly standardized, and now when&lt;br&gt;
people try to do something, everything is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking back, what we have now
&lt;/h2&gt;

&lt;p&gt;I know that there are a lot of problems on the Internet, it is growing&lt;br&gt;
fast, and it is very common that we will throw things away when we do&lt;br&gt;
not need them anymore.&lt;/p&gt;

&lt;p&gt;The issue is that when we throw away things, we do not stop depending on&lt;br&gt;
it, while the attention is driven to new projects, the use of old ones&lt;br&gt;
never stops, but the development slows or stops.&lt;/p&gt;

&lt;p&gt;We constantly &lt;strong&gt;invent&lt;/strong&gt; new things, and shift attention to them. But we&lt;br&gt;
do not stop using them, causing a lot of old code to restrict the&lt;br&gt;
development of new ones.&lt;/p&gt;

&lt;p&gt;Until now, I want to reference a meme picture from the Cloudflare and&lt;br&gt;
AWS outages, but the thin cylinder is the old software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk11ywcean38qtst7vem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk11ywcean38qtst7vem.png" alt="Internet Outage Meme" width="274" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the worse part about this is that when the fragile part is AWS or&lt;br&gt;
Cloudflare, they have engineers waiting 24/7 to fix the problem, but for&lt;br&gt;
old software, no one could fix it in time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we solve it? Or do we need to solve it?
&lt;/h2&gt;

&lt;p&gt;This is basically the entire post, what I want to say is that these all&lt;br&gt;
happened because the Internet had developed too fast. That people did&lt;br&gt;
not have time to absorb all the information and software already&lt;br&gt;
created, and new ones had already been created..&lt;/p&gt;

&lt;p&gt;What I think is that we should care about the old but software that you&lt;br&gt;
use every day, you should always know that behind these software, there&lt;br&gt;
are always 1 or few people working for it&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full more detailed version of this blog containing more of my opinions is available on &lt;a href="https://blog.vyang.org/posts/lonely-places-of-the-internet.php" rel="noopener noreferrer"&gt;vyang.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
      <category>writing</category>
    </item>
    <item>
      <title>Building Bootable Images in Userspace: Avoiding the Loop Device Trap</title>
      <dc:creator>Vincent Yang</dc:creator>
      <pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/vincent4486/building-a-disk-image-without-root-privilege-40kp</link>
      <guid>https://dev.to/vincent4486/building-a-disk-image-without-root-privilege-40kp</guid>
      <description>&lt;h2&gt;
  
  
  Building Disk Images Without Root (And Without Regret)
&lt;/h2&gt;

&lt;p&gt;If you have built enough disk images, you eventually hit the same &lt;strong&gt;Rite Of Passage&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It is 1:27 AM, you are tired, and you want run a fast "one-liner" with &lt;code&gt;sudo dd&lt;/code&gt; or &lt;code&gt;sudo mkfs.fat&lt;/code&gt;.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;You falsely typed one character.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;You did not wipe the test image.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;You wipe a host partition.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That story is so common it might as well be onboarding material.&lt;/p&gt;

&lt;p&gt;The trap is not triggered because low-level tools are bad. But because the trap is running low-level tools with maximum privilege on the host, in a workflow with high typo probability and weak guardrails.&lt;/p&gt;

&lt;p&gt;This guide is about building bootable disk images in &lt;strong&gt;User Space&lt;/strong&gt; while keeping &lt;strong&gt;Host Safety&lt;/strong&gt; intact. And not get your host disk ruined.&lt;/p&gt;
&lt;h2&gt;
  
  
  Failure Mode: The Root Workflow Drift
&lt;/h2&gt;

&lt;p&gt;Most people start with this pattern because it looks "standard":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Create a blank disk image&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disk.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512

&lt;span class="c"&gt;# 2) Partition it&lt;/span&gt;
parted &lt;span class="nt"&gt;-s&lt;/span&gt; disk.img mklabel msdos mkpart primary fat32 1MiB 100%

&lt;span class="c"&gt;# 3) Bind with a loop device&lt;/span&gt;
&lt;span class="nv"&gt;loop_dev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;losetup &lt;span class="nt"&gt;-fP&lt;/span&gt; &lt;span class="nt"&gt;--show&lt;/span&gt; disk.img&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 4) Make filesystem + mount + copy&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkfs.fat &lt;span class="nt"&gt;-F&lt;/span&gt; 32 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;loop_dev&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;p1"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./mnt
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;loop_dev&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;p1"&lt;/span&gt; ./mnt
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./payload/. ./mnt/
&lt;span class="nb"&gt;sudo &lt;/span&gt;umount ./mnt
&lt;span class="nb"&gt;sudo &lt;/span&gt;losetup &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;loop_dev&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here is unusual. That is exactly why it is dangerous, because what you &lt;strong&gt;think&lt;/strong&gt; is not dangerous could be &lt;strong&gt;very&lt;/strong&gt; dangerous. This sequence normalizes a habit where every critical operation uses &lt;code&gt;sudo&lt;/code&gt;, so one typo has host-level blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hierarchy of Hazard
&lt;/h2&gt;

&lt;p&gt;Use a &lt;strong&gt;Symptom Vs Catastrophe&lt;/strong&gt; model when evaluating root-heavy image workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1: Annoying Symptom
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Wrong partition offset in &lt;code&gt;dd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Files copied into the wrong path.&lt;/li&gt;
&lt;li&gt;Disk not booting due to broken VBR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are fixable and mostly local to the image artifact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2: Costly Symptom
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Loop device leaks because &lt;code&gt;losetup -d&lt;/code&gt; was skipped.&lt;/li&gt;
&lt;li&gt;Confusing &lt;code&gt;/dev/loopN&lt;/code&gt; state after repeated runs.&lt;/li&gt;
&lt;li&gt;CI/CD scripts becoming non-deterministic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now your workflow degrades over time and debugging cost increases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 3: Catastrophe
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mkfs&lt;/code&gt; points to a host partition instead of the image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dd&lt;/code&gt; writes to the wrong block device.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grub-install&lt;/code&gt; targets host storage after device resolution fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this tier, you are no longer "debugging an image." You are performing incident response on your own workstation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Usually Breaks First
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Loopback Device Lifecycle
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What Breaks
&lt;/h4&gt;

&lt;p&gt;Loop devices accumulate when scripts exit early or cleanup paths are missed. Eventually your flow depends on stale &lt;code&gt;/dev/loopN&lt;/code&gt; state and implicit assumptions about which loop index maps to which file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why it Matters
&lt;/h4&gt;

&lt;p&gt;Your build stops being deterministic. On a good day it "works on my machine". On a bad day it formats the wrong target because &lt;code&gt;loop_dev&lt;/code&gt; is not what your script thinks it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  GRUB Target Resolution
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What Breaks
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;grub-install&lt;/code&gt; is called with an invalid or unexpected target device, then falls back to behavior you did not intend, &lt;strong&gt;GRUB will fallback to your host disk if the disk you specified to GRUB does not exist.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Why it Matters
&lt;/h4&gt;

&lt;p&gt;Bootloader writes are destructive by design. A mis-targeted install can modify host boot configuration and convert a test run into a host recovery task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three User Space Strategies
&lt;/h2&gt;

&lt;p&gt;The goal is simple: no &lt;code&gt;mount(2)&lt;/code&gt;, no host loop device dependency for routine file operations, and minimal root privilege.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Libguestfs (&lt;code&gt;guestfish&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;guestfish&lt;/code&gt; builds and edits disk images inside an appliance VM. That isolation is the key safety property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;guestfish &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
sparse disk.img 512M
run
part-init /dev/sda mbr
part-add /dev/sda p 2048 -1
mkfs vfat /dev/sda1
mount /dev/sda1 /
copy-in payload/. /
sync
quit
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Strong &lt;strong&gt;Host Safety&lt;/strong&gt; boundary because operations happen in a guest appliance.&lt;/li&gt;
&lt;li&gt;Scriptable for CI with stable command primitives.&lt;/li&gt;
&lt;li&gt;Good default for "just make me a bootable image" pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Slower on weak hosts because all I/O is VM-mediated.&lt;/li&gt;
&lt;li&gt;Kernel/appliance setup can be brittle on some distros.&lt;/li&gt;
&lt;li&gt;FAT behavior can become MysteryMeat in edge sizes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On that FAT point: &lt;code&gt;mkfs vfat&lt;/code&gt; may auto-select FAT12/FAT16/FAT32 based on partition geometry. You can end up with a FAT16-style VBR while the partition type marker says &lt;code&gt;0x0c&lt;/code&gt; (FAT32 LBA). The image is technically "built," but tooling assumptions diverge and boot behavior becomes inconsistent, if you have your own OS, it may be a problem.&lt;/p&gt;

&lt;h4&gt;
  
  
  When to use this
&lt;/h4&gt;

&lt;p&gt;Use when you want maximum safety and can tolerate lower build speed, it is also good when you want maximum customizability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: DiskCombining (Assemble Image Segments)
&lt;/h3&gt;

&lt;p&gt;This method creates partition payloads separately, then injects them into a pre-partitioned disk image. It favors explicit geometry control and reproducibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;disk_size_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512
&lt;span class="nv"&gt;part_size_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;511
&lt;span class="nv"&gt;part_start_lba&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2048

&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disk.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disk_size_mb&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create partition table non-interactively&lt;/span&gt;
parted &lt;span class="nt"&gt;-s&lt;/span&gt; disk.img mklabel msdos
parted &lt;span class="nt"&gt;-s&lt;/span&gt; disk.img mkpart primary fat32 1MiB 100%

&lt;span class="c"&gt;# Build partition payload independently&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;part.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;part_size_mb&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
mkfs.fat &lt;span class="nt"&gt;-F&lt;/span&gt; 32 part.img

&lt;span class="c"&gt;# Populate partition payload using user-space FAT tools&lt;/span&gt;
mmd &lt;span class="nt"&gt;-i&lt;/span&gt; part.img ::/boot
mcopy &lt;span class="nt"&gt;-i&lt;/span&gt; part.img kernel.bin ::/boot/kernel.bin

&lt;span class="c"&gt;# Inject partition payload at the partition start&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;part.img &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disk.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512 &lt;span class="nv"&gt;seek&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;part_start_lba&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;conv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;notrunc

&lt;span class="c"&gt;# Set partition type to W95 FAT32 (LBA)&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\x0c'&lt;/span&gt; | &lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disk.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;seek&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;450 &lt;span class="nv"&gt;conv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;notrunc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Maximum control over geometry, offsets, and byte-level layout.&lt;/li&gt;
&lt;li&gt;Fast and deterministic once script variables are correct.&lt;/li&gt;
&lt;li&gt;Works well for advanced boot experiments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Higher cognitive load; offset math mistakes are easy.&lt;/li&gt;
&lt;li&gt;MBR-first workflows are simpler here than GPT-first workflows.&lt;/li&gt;
&lt;li&gt;You own all correctness checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  When to use this
&lt;/h4&gt;

&lt;p&gt;Use when you need precise layout control and are comfortable validating disk geometry manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: mtools-First FAT Workflow
&lt;/h3&gt;

&lt;p&gt;This is the most underrated path for FAT boot media. Use &lt;code&gt;mtools&lt;/code&gt; to manipulate filesystems entirely in UserSpace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create FAT image file directly&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;part.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64
mkfs.fat &lt;span class="nt"&gt;-F&lt;/span&gt; 32 part.img

&lt;span class="c"&gt;# No mount syscall required&lt;/span&gt;
mmd &lt;span class="nt"&gt;-i&lt;/span&gt; part.img ::/EFI
mmd &lt;span class="nt"&gt;-i&lt;/span&gt; part.img ::/EFI/BOOT
mcopy &lt;span class="nt"&gt;-i&lt;/span&gt; part.img BOOTX64.EFI ::/EFI/BOOT/BOOTX64.EFI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mcopy&lt;/code&gt; and &lt;code&gt;mmd&lt;/code&gt; operate without &lt;code&gt;mount&lt;/code&gt;, so no root requirement for file operations.&lt;/li&gt;
&lt;li&gt;Cleaner CI scripting because there is no loop device lifecycle to manage.&lt;/li&gt;
&lt;li&gt;Excellent for FAT payload staging before final disk assembly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Primarily useful for FAT workflows; not a universal filesystem toolkit.&lt;/li&gt;
&lt;li&gt;Less familiar to developers used to &lt;code&gt;mount&lt;/code&gt; + &lt;code&gt;cp&lt;/code&gt; habits.&lt;/li&gt;
&lt;li&gt;You still need a partitioning plan for full-disk images.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  When to use this
&lt;/h4&gt;

&lt;p&gt;Use when your target is FAT-based boot media and you want the safest day-to-day developer loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Verdict
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you optimize for &lt;strong&gt;Safety First&lt;/strong&gt;: choose &lt;code&gt;guestfish&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you optimize for &lt;strong&gt;Layout Control&lt;/strong&gt;: choose Disk Combining.&lt;/li&gt;
&lt;li&gt;If you optimize for daily velocity on FAT images: choose &lt;code&gt;mtools&lt;/code&gt; + assembly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My default in real projects is hybrid:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;mtools&lt;/code&gt; for payload edits.&lt;/li&gt;
&lt;li&gt;Use Disk Combining for reproducible final image assembly.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;guestfish&lt;/code&gt; as the "safe fallback" when environment-specific tools become noisy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps the core loop in UserSpace while preserving deep control when needed.&lt;/p&gt;

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

&lt;p&gt;Root privileges are not a badge of low-level skill. They are a liability multiplier.&lt;/p&gt;

&lt;p&gt;If your disk workflow requires frequent &lt;code&gt;sudo&lt;/code&gt;, you are one typo away from turning a build task into a recovery task. Shift routine image operations to UserSpace, isolate risky steps, and reserve privilege for the rare places where it is truly necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiments &amp;amp; Resources
&lt;/h3&gt;

&lt;p&gt;I spend roughly 6 hours a week outside my main work researching these systems performance bottlenecks and refining my build toolchain. For those building for unpackage, I’ve packaged my advanced, rootless disk-building scripts—along with the full source for this experiment into the latest code bundle on my &lt;a href="https://support.vyang.org/" rel="noopener noreferrer"&gt;Support&lt;/a&gt; page. It’s the fastest way to move from this theory to a repeatable production pipeline.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>osdev</category>
      <category>software</category>
      <category>cli</category>
    </item>
    <item>
      <title>Setup OSDev Environment on macOS</title>
      <dc:creator>Vincent Yang</dc:creator>
      <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/vincent4486/setup-osdev-environment-on-macos-132j</link>
      <guid>https://dev.to/vincent4486/setup-osdev-environment-on-macos-132j</guid>
      <description>&lt;h2&gt;
  
  
  Why OSDev on macOS works (if you stop fighting defaults)
&lt;/h2&gt;

&lt;p&gt;The failure mode is always the same: you follow an OSDev tutorial, hit &lt;code&gt;make&lt;/code&gt;, and end up with a binary that either won’t boot, won’t link, or isn’t even in the format your bootloader expects.&lt;/p&gt;

&lt;p&gt;macOS is a great &lt;em&gt;editor&lt;/em&gt; and &lt;em&gt;workflow&lt;/em&gt; environment. But it is a terrible &lt;em&gt;default&lt;/em&gt; build target for classic OSDev.&lt;/p&gt;

&lt;p&gt;This guide builds a layered mental model for the “why”, then gives three setups (from lightest to heaviest) that I’ve used successfully: a cross-toolchain on macOS, a Docker build container, and a Linux VM via Lima.&lt;/p&gt;




&lt;h2&gt;
  
  
  The two hard constraints (the stack you can’t ignore)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) CPU architecture: aarch64 vs x86
&lt;/h3&gt;

&lt;p&gt;Modern Macs (M1/M2/M3/M4) are &lt;strong&gt;aarch64 (ARM64)&lt;/strong&gt; machines. Most OSDev examples, bootloaders, and “hello kernel” walkthroughs targets &lt;strong&gt;i686/x86_64&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What breaks:&lt;/strong&gt; Native &lt;code&gt;clang&lt;/code&gt;/&lt;code&gt;ld&lt;/code&gt; produce ARM machine code by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it matters:&lt;/strong&gt; An x86 emulator (or real x86 hardware) can’t execute ARM opcodes. If your kernel is the wrong ISA, you won’t get “a little wrong” behavior—you’ll get a hard stop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are targeting ARM &lt;em&gt;on purpose&lt;/em&gt;, you still have the second constraint.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Executable format: Mach-O vs ELF
&lt;/h3&gt;

&lt;p&gt;macOS uses &lt;strong&gt;Mach-O&lt;/strong&gt;. Most OSDev toolchains and boot flows assume &lt;strong&gt;ELF&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ELF:&lt;/strong&gt; The common format for Linux/BSD tooling and OSDev build systems.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mach-O:&lt;/strong&gt; The native macOS format, tightly integrated with Apple’s linker/loader and platform conventions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What breaks:&lt;/strong&gt; Bootloaders, kernels, and link scripts in OSDev projects often expect ELF sections/symbol layout and ELF headers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nuclear option (don’t do this):&lt;/strong&gt; Write your own linker (or a Mach-O loader in your boot path) just to make the native toolchain fit.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Three working setups (from native → isolated → maximum compatibility)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Cross-compiler on macOS (fastest feedback loop)
&lt;/h3&gt;

&lt;p&gt;This is the “keep everything local” setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editor:&lt;/strong&gt; macOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build toolchain:&lt;/strong&gt; &lt;code&gt;i686-elf-*&lt;/code&gt; or &lt;code&gt;x86_64-elf-*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; ELF binaries for your target&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Path A: Homebrew (the quick start)
&lt;/h4&gt;

&lt;p&gt;If all you need is a usable cross toolchain, Homebrew is the shortest path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;i686-elf-binutils i686-elf-gcc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt; Minutes to set up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Less control. Once you start building a libc, switching freestanding/hosted modes, or pinning specific versions, you’ll want a source build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Path B: Build from source (the “I want it correct” path)
&lt;/h4&gt;

&lt;p&gt;This is the common OSDev approach: build &lt;strong&gt;binutils first&lt;/strong&gt;, then build &lt;strong&gt;GCC&lt;/strong&gt; against it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this order matters&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binutils&lt;/strong&gt; provides &lt;code&gt;as&lt;/code&gt; (assembler), &lt;code&gt;ld&lt;/code&gt; (linker), and friends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCC&lt;/strong&gt; will call into those tools when producing final objects/binaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Binutils example (i686-elf)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Native compiler toolchain for building tools&lt;/span&gt;
xcode-select &lt;span class="nt"&gt;--install&lt;/span&gt;

&lt;span class="c"&gt;# Fetch sources&lt;/span&gt;
wget https://ftp.gnu.org/gnu/binutils/binutils-2.45.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvf&lt;/span&gt; binutils-2.45.tar.gz

&lt;span class="c"&gt;# Out-of-tree build keeps your source directory clean&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; binutils-2.45/build
&lt;span class="nb"&gt;cd &lt;/span&gt;binutils-2.45/build

&lt;span class="c"&gt;# Key flags:&lt;/span&gt;
&lt;span class="c"&gt;# --target=i686-elf    =&amp;gt; produce tools that emit i686 code and ELF output&lt;/span&gt;
&lt;span class="c"&gt;# --prefix=...         =&amp;gt; install into an isolated directory&lt;/span&gt;
&lt;span class="c"&gt;# --disable-nls        =&amp;gt; disables Native Language Support (fewer deps, faster build)&lt;/span&gt;
&lt;span class="c"&gt;# --disable-werror     =&amp;gt; warnings won't fail the build on strict compilers&lt;/span&gt;
../configure &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;i686-elf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/cross &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-nls&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disable-werror&lt;/span&gt;

&lt;span class="c"&gt;# macOS doesn't ship `nproc` by default&lt;/span&gt;
make &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; hw.ncpu&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat the same pattern for GCC, pointing it at the same &lt;code&gt;--target&lt;/code&gt; and &lt;code&gt;--prefix&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Option 2: Docker (portable + reproducible)
&lt;/h3&gt;

&lt;p&gt;Docker gives you a clean Linux build environment while keeping your source on the host.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What you gain:&lt;/strong&gt; A pinned, shareable toolchain across machines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you lose:&lt;/strong&gt; Some performance (especially if you force x86 emulation on Apple Silicon).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Minimal Dockerfile skeleton
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; --platform=linux/amd64 ubuntu:latest&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    wget &lt;span class="se"&gt;\
&lt;/span&gt;    nasm &lt;span class="se"&gt;\
&lt;/span&gt;    qemu-system-x86

&lt;span class="c"&gt;# Toolchain install/build goes here:&lt;/span&gt;
&lt;span class="c"&gt;# - install prebuilt x86_64-elf toolchain packages, OR&lt;/span&gt;
&lt;span class="c"&gt;# - build binutils+gcc from source inside the image&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /osdev&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Build + run
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; osdev-env &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# -v maps your current folder into the container&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/osdev osdev-env make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;--platform=linux/amd64&lt;/code&gt; matters&lt;/strong&gt;&lt;br&gt;
On Apple Silicon, this forces an x86_64 userland. That can increase compatibility with prebuilt toolchains, but it may run slower due to emulation.&lt;/p&gt;


&lt;h3&gt;
  
  
  Option 3: Linux VM via Lima (maximum compatibility)
&lt;/h3&gt;

&lt;p&gt;If you want the least “mystery meat” behavior, stop fighting macOS and build inside Linux.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is the nuclear option for compatibility:&lt;/strong&gt; it matches the environment most OSDev docs assume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow:&lt;/strong&gt; edit on macOS, build/run in Linux, attach VS Code via SSH.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Install Lima
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;lima
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create and enter a VM
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;limactl start &lt;span class="nt"&gt;--name&lt;/span&gt; osdev template://ubuntu
limactl shell osdev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Mount your source directory (host ↔ guest)
&lt;/h4&gt;

&lt;p&gt;Edit the instance config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;limactl edit osdev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a writable mount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/path/to/your/os/source/code&lt;/span&gt;
    &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  VS Code integration
&lt;/h4&gt;

&lt;p&gt;Add to your SSH config so your editor can see Lima hosts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;Include&lt;/span&gt; ~/.lima/*/ssh.config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editor UI:&lt;/strong&gt; macOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build tools + QEMU:&lt;/strong&gt; inside the VM&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Run QEMU inside the VM
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; qemu-system-x86

&lt;span class="c"&gt;# Example: adjust to your boot flow / image layout&lt;/span&gt;
qemu-system-x86_64 &lt;span class="nt"&gt;-m&lt;/span&gt; 512M &lt;span class="nt"&gt;-net&lt;/span&gt; none &lt;span class="nt"&gt;-kernel&lt;/span&gt; /path/to/your/os/kernel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Pick the lightest setup that gives you deterministic results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-compiler:&lt;/strong&gt; Best day-to-day speed on macOS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker:&lt;/strong&gt; Best reproducibility for teams and CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lima VM:&lt;/strong&gt; Best compatibility when you’re deep in toolchain/bootloader land.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a full Linux host on Apple Silicon without virtualization, &lt;strong&gt;Asahi Linux&lt;/strong&gt; is the endgame.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiments &amp;amp; Resources
&lt;/h3&gt;

&lt;p&gt;To keep this post focused on the core concepts, I’ve hosted the full &lt;strong&gt;code bundles&lt;/strong&gt;, and early-access development logs on my personal site. If you're looking to run these you can find everything here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.vyang.org/index.php" rel="noopener noreferrer"&gt;View Project Files &amp;amp; Early Access&lt;/a&gt;&lt;/p&gt;

</description>
      <category>osdev</category>
      <category>programming</category>
      <category>docker</category>
      <category>virtualmachine</category>
    </item>
    <item>
      <title>Website Deployed but Nothing Changed</title>
      <dc:creator>Vincent Yang</dc:creator>
      <pubDate>Sat, 14 Mar 2026 02:08:00 +0000</pubDate>
      <link>https://dev.to/vincent4486/website-deployed-but-nothing-changed-26pm</link>
      <guid>https://dev.to/vincent4486/website-deployed-but-nothing-changed-26pm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you have built a website, you have likely encountered the specific frustration of deploying to the global network: you update a CSS style or patch a JavaScript bug, but the production site stubbornly serves the old version. Only your users see the "broken" state. This is especially painful during emergency hotfixes or when debugging environment-specific issues on platforms like PHP.&lt;/p&gt;

&lt;p&gt;I personally lost hours debugging this exact blog, simple PHP and CSS because of stale code. What takes 1 hour on localhost can take 5 in production. On localhost, the loop is tight. On the open web, you are fighting against multiple layers of optimization designed to prevent exactly what you are trying to do. Which is download fresh files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack Trace of Caching
&lt;/h2&gt;

&lt;p&gt;If your site isn't updating, work through the failure points in this order, from "User Error" to "Architecture".&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Failed to Deploy
&lt;/h3&gt;

&lt;p&gt;Before blaming the network, blame the pipeline. Despite the title "deployed but nothing changed", there is a non-zero chance you haven't actually deployed.&lt;/p&gt;

&lt;p&gt;Did the GitHub Action runner actually turn green? Did the &lt;code&gt;rsync&lt;/code&gt; script fail silently due to permissions or clock skew?&lt;br&gt;
&lt;strong&gt;Always verify the file modification time on the server first.&lt;/strong&gt; &lt;code&gt;ls -l&lt;/code&gt; can help you check the file time. If the file is old on the disk, no amount of cache purging will fix the client.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Caching Layers
&lt;/h3&gt;

&lt;p&gt;The most common culprit is &lt;strong&gt;caching&lt;/strong&gt;. It's not a single entity; it is a stack. You have Server caching (PHP OpCode), Load Balancers, DNS, CDNs, ISP transparent proxies, and finally, the user's browser. Even one stale layer breaks the chain.&lt;/p&gt;
&lt;h4&gt;
  
  
  Layer 1: The Browser Cache
&lt;/h4&gt;

&lt;p&gt;Browsers aggressively cache static assets (CSS, JS, Images) to save battery and data.&lt;br&gt;
&lt;strong&gt;For Local Debugging:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard Reload:&lt;/strong&gt; Force the browser to bypass its cache. &lt;code&gt;Ctrl + Shift + R&lt;/code&gt; (Windows/Linux) or &lt;code&gt;Command + Shift + R&lt;/code&gt; (macOS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevTools:&lt;/strong&gt; Open the Network tab in your browser's developer tools and check &lt;strong&gt;"Disable cache"&lt;/strong&gt;. As long as that pane is open, you will get fresh files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Production (The Configuration Fix):&lt;/strong&gt;&lt;br&gt;
To ensure all clients see changes immediately, you must instruct the browser not to trust its local copy. You do this with the &lt;code&gt;Cache-Control&lt;/code&gt; HTTP header. The directive &lt;code&gt;no-cache, no-store, must-revalidate&lt;/code&gt; is the nuclear option for ensuring freshness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nginx Configuration:&lt;/strong&gt;&lt;br&gt;
Add this to your &lt;code&gt;server&lt;/code&gt; or &lt;code&gt;location&lt;/code&gt; block for your static assets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(css|js|png)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"no-cache,&lt;/span&gt; &lt;span class="s"&gt;no-store,&lt;/span&gt; &lt;span class="s"&gt;must-revalidate"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Apache Configuration (.htaccess):&lt;/strong&gt;&lt;br&gt;
Ensure &lt;code&gt;mod_headers&lt;/code&gt; is enabled and add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="sr"&gt; "\.(css|js|png)$"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod_headers.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Cache-Control "no-cache, no-store, must-revalidate"
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;FilesMatch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: This forces every client to hit your server for every asset, maximizing freshness but increasing server load.&lt;/em&gt;&lt;/p&gt;



&lt;h4&gt;
  
  
  Layer 2: Network &amp;amp; Edge Caches (CDNs)
&lt;/h4&gt;

&lt;p&gt;If you use a CDN (like Cloudflare, AWS CloudFront, or Fastly), setting headers on your origin server isn't enough if the CDN has already cached the old file. The &lt;code&gt;Cache-Control&lt;/code&gt; header will eventually propagate, but for immediate fixes, you must &lt;strong&gt;Purge&lt;/strong&gt; the cache.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Log into your CDN dashboard and look for "Purge Cache" or "Invalidate". You can usually purge a specific URL (lighter) or the entire zone (nuclear).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy:&lt;/strong&gt; Don't rely on manual purges. Configure your CDN to respect your &lt;code&gt;Cache-Control&lt;/code&gt; headers, or better yet, use Versioning (see below).&lt;/li&gt;
&lt;/ul&gt;



&lt;h4&gt;
  
  
  Layer 3: Server-Side Caching (OpCode)
&lt;/h4&gt;

&lt;p&gt;For interpreted languages like PHP, the server often caches the compiled bytecode (OpCode) in memory to avoid parsing the script on every request.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Symptom:&lt;/strong&gt; You updated &lt;code&gt;index.php&lt;/code&gt;, the file on disk is new, but the server returns the old logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Fix:&lt;/strong&gt; You may need to restart your PHP service (&lt;code&gt;service php-fpm restart&lt;/code&gt;) or tune your &lt;code&gt;opcache.revalidate_freq&lt;/code&gt; setting in &lt;code&gt;php.ini&lt;/code&gt; to check for file changes more frequently.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Ultimate Fix: Versioning (Cache Busting)
&lt;/h3&gt;

&lt;p&gt;Purging caches and wrestling with headers is reactive. The industry-standard solution is &lt;strong&gt;Versioning&lt;/strong&gt; (or Cache Busting).&lt;/p&gt;

&lt;p&gt;Browsers identify files by URL.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;style.css&lt;/code&gt; = Browser checks cache.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;style.v2.css&lt;/code&gt; = Browser treats it as a new resource.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to manually rename files. Since you are using PHP, automate it by appending the file's &lt;strong&gt;Modification Time&lt;/strong&gt; as a query parameter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The PHP Auto-Versioning Strategy:&lt;/strong&gt;&lt;br&gt;
Instead of a static link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inject the file's timestamp dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css?v=&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;filemtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'style.css'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How this solves everything:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;filemtime&lt;/code&gt; reads the Unix timestamp of the last save (e.g., &lt;code&gt;1678123456&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The rendered HTML is &lt;code&gt;&amp;lt;link href="style.css?v=1678123456"&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;As long as you don't touch the file, the URL stays the same (Cache Hit).&lt;/li&gt;
&lt;li&gt;The second you deploy a change, the timestamp updates, the URL changes, and &lt;strong&gt;every&lt;/strong&gt; browser, CDN, and proxy is forced to fetch the new version instantly.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Debugging deployment issues is about eliminating variables.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt; the file is actually updated on the server disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bypass&lt;/strong&gt; your local cache (DevTools).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control&lt;/strong&gt; downstream caching with &lt;code&gt;Cache-Control&lt;/code&gt; headers (Nginx/Apache).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate&lt;/strong&gt; freshness with Cache Busting version strings.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>nginx</category>
    </item>
  </channel>
</rss>
