<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Mac on Code is cheap, let&#39;s talk</title>
    <link>https://blog.ferstar.org/en/tags/mac/</link>
    <description>Code is cheap, let&#39;s talk</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <copyright>Copyright 2026 ferstar</copyright>
    <lastBuildDate>Sat, 13 Jun 2026 18:40:39 +0800</lastBuildDate>
    <ttl>60</ttl><atom:link href="https://blog.ferstar.org/en/tags/mac/index.xml" rel="self" type="application/rss+xml" /><image>
      <url>https://blog.ferstar.org/site-logo.png</url>
      <title>Code is cheap, let&#39;s talk</title>
      <link>https://blog.ferstar.org/</link>
    </image>
    
    <item>
      <title>Moving from OrbStack to Apple Container, with PostgreSQL in Tow</title>
      <link>https://blog.ferstar.org/en/posts/orbstack-to-apple-container-postgres/</link>
      <pubDate>Sat, 13 Jun 2026 18:40:39 +0800</pubDate>
      
      <guid isPermaLink="true">https://blog.ferstar.org/en/posts/orbstack-to-apple-container-postgres/</guid>
      <description>OrbStack was ready to go, but the local PostgreSQL data had to survive; I moved it to Apple Container, disabled local WAL archiving, and freed about 19GiB of disk space.</description><content:encoded><![CDATA[<blockquote><p>I am not a native English speaker; this article was translated by AI.</p>
</blockquote><p>Today I removed OrbStack from my Mac.</p>
<p>Not because it was bad. Quite the opposite: for the past few years, OrbStack has been the least annoying Docker replacement I have used on macOS. But Apple’s own <code>container</code> CLI can now run Linux containers, and the only long-running local service I still had was PostgreSQL. With the dependency surface already this small, it made sense to shrink the runtime too.</p>
<p>The dangerous version of this migration would be: “just delete it and recreate it.” Containers can be recreated. Databases cannot.</p>

<h3 class="relative group">First, check what is actually running
    <div id="first-check-what-is-actually-running" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#first-check-what-is-actually-running" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>There was only one long-running container in OrbStack:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker ps -a --format <span class="s1">'{{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}'</span></span></span></code></pre></div></div>
<p>The result was simple:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">d377e0104620  registry.cheftin.cn/p/postgres17_pd-arm64  scriber_pg  Up 2 weeks (healthy)</span></span></code></pre></div></div>
<p>Then <code>docker inspect</code> exposed the trap: <code>docker-compose.yml</code> mounted <code>./pg17:/var/lib/postgresql/data</code>, but the image actually used <code>PGDATA=/data/data</code>. In other words, the host-side <code>pg17</code> directory was empty, while the real database lived inside the old container under <code>/data</code>.</p>
<p>That almost turned the migration into an incident.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">PGDATA=/data/data
</span></span><span class="line"><span class="cl">/Users/ferstar/myprojects/postgresql/pg17  0B
</span></span><span class="line"><span class="cl">/data                                 7.2G</span></span></code></pre></div></div>
<p>So I could not simply stop OrbStack and remount the same host directory. I had to export the data from the running old container first.</p>

<h3 class="relative group">Backups first
    <div id="backups-first" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#backups-first" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>I made two backups:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> scriber_pg pg_dumpall -U postgres <span class="p">|</span> gzip -9 > pg_dumpall.sql.gz
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> scriber_pg tar -C / -cf - data <span class="p">|</span> zstd -T0 -10 > container-data.tar.zst</span></span></code></pre></div></div>
<p>The logical backup was only <code>38M</code>; the physical archive was <code>6.8G</code>. At first I thought the database was huge. It was not.</p>
<p>The largest part of <code>/data</code> was not business data, but the image’s own local WAL archive:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">6865.7M  data/backup/wal
</span></span><span class="line"><span class="cl">293.5M   data/data/base
</span></span><span class="line"><span class="cl">126.7M   data/data/log
</span></span><span class="line"><span class="cl">32.0M    data/data/pg_wal</span></span></code></pre></div></div>
<p>The actual table data was only a few hundred MB.</p>

<h3 class="relative group">Apple Container takes over
    <div id="apple-container-takes-over" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#apple-container-takes-over" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This machine is on macOS 15.7.3. Apple’s <code>container</code> project is more officially aimed at macOS 26, but Homebrew can already install it:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew install container
</span></span><span class="line"><span class="cl">container system start</span></span></code></pre></div></div>
<p>The first startup installs the default kernel. After a small Alpine test passed, I exported the PostgreSQL image from local Docker and loaded it into Apple Container, avoiding private registry credential issues:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker save registry.cheftin.cn/p/postgres17_pd-arm64:latest -o postgres17_pd-arm64.docker-save.tar
</span></span><span class="line"><span class="cl">container image load -i postgres17_pd-arm64.docker-save.tar</span></span></code></pre></div></div>
<p>The final container is launched like this:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">container run -d <span class="se">\
</span></span></span><span class="line"><span class="cl">  --name scriber_pg <span class="se">\
</span></span></span><span class="line"><span class="cl">  --memory 4g <span class="se">\
</span></span></span><span class="line"><span class="cl">  --cpus <span class="m">4</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --shm-size 8g <span class="se">\
</span></span></span><span class="line"><span class="cl">  -p 0.0.0.0:5432:5432 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -v /Users/ferstar/myprojects/postgresql/apple-pg17:/data <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">DEBUG</span><span class="o">=</span><span class="nb">false</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span>... <span class="se">\
</span></span></span><span class="line"><span class="cl">  -e <span class="nv">POSTGRES_HOST_AUTH_METHOD</span><span class="o">=</span>trust <span class="se">\
</span></span></span><span class="line"><span class="cl">  registry.cheftin.cn/p/postgres17_pd-arm64:latest <span class="se">\
</span></span></span><span class="line"><span class="cl">  postgres -c <span class="nv">archive_mode</span><span class="o">=</span>off -c <span class="nv">archive_command</span><span class="o">=</span></span></span></code></pre></div></div>
<p>The last two <code>-c</code> options matter.</p>
<p>This image rewrites <code>postgresql.conf</code> during startup, so editing the file directly inside the data directory gets overwritten. Putting <code>archive_mode=off</code> in PostgreSQL command-line arguments makes the setting source <code>command line</code>, and it stays stable after restart.</p>
<p>Verification:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">archive_command=(disabled) source=command line
</span></span><span class="line"><span class="cl">archive_mode=off source=command line
</span></span><span class="line"><span class="cl">wal_level=logical source=configuration file</span></span></code></pre></div></div>
<p>I left <code>wal_level=logical</code> alone. It was not the source of the local backup growth, and disabling it casually could break logical replication or extensions.</p>

<h3 class="relative group">One more piece for autostart
    <div id="one-more-piece-for-autostart" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#one-more-piece-for-autostart" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Apple Container does not yet feel like Docker Compose with <code>restart: unless-stopped</code>. <code>brew services start container</code> brings up the apiserver at login, but it does not guarantee a specific container starts.</p>
<p>So I added a user LaunchAgent:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/Library/LaunchAgents/com.ferstar.apple-container.scriber-pg.plist</span></span></code></pre></div></div>
<p>It runs a tiny script:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">container system start >/tmp/scriber_pg_container_system_start.log 2><span class="p">&</span><span class="m">1</span> <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> container ls --quiet <span class="p">|</span> grep -Fxq <span class="s1">'scriber_pg'</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">container start scriber_pg</span></span></code></pre></div></div>
<p>Not elegant, but enough.</p>

<h3 class="relative group">The final bill
    <div id="the-final-bill" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-final-bill" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Then OrbStack was uninstalled and zapped:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brew uninstall --cask --zap orbstack</span></span></code></pre></div></div>
<p>I also removed leftovers under <code>~/.orbstack</code>, <code>~/OrbStack</code>, and related Group Containers directories. Since the local Docker CLI came from OrbStack, the <code>docker</code> command disappeared too. That is fine; daily usage now only needs <code>container</code>.</p>
<p>Cleanup result:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Free space at the tightest point:  6.8GiB
</span></span><span class="line"><span class="cl">After deleting physical rollback archive: 14GiB
</span></span><span class="line"><span class="cl">After uninstalling OrbStack:       26GiB
</span></span><span class="line"><span class="cl">Total freed:                       about 19GiB</span></span></code></pre></div></div>
<p>Current footprint:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Apple Container runtime data: 3.8G
</span></span><span class="line"><span class="cl">New PostgreSQL data directory: 274M
</span></span><span class="line"><span class="cl">Migration logical backups/logs: 76M
</span></span><span class="line"><span class="cl">scriber_pg actual memory: about 148MiB / 4GiB
</span></span><span class="line"><span class="cl">scriber_pg CPU: 0.00%</span></span></code></pre></div></div>
<p>The databases are still there:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">postgres|11</span></span></code></pre></div></div>

<h3 class="relative group">Summary
    <div id="summary" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#summary" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The biggest lesson here was not how to use Apple Container. It was: do not trust the volume path in a compose file at face value. Check the running container’s real <code>PGDATA</code> and mounts first.</p>
<p>The other lesson was to distinguish between WAL that PostgreSQL needs to run and WAL that an image archives elsewhere as backup. The former must stay. The latter should be disabled if local backups are not needed. Otherwise, a few-hundred-MB database can eventually scare you with several GB of archived WAL.</p>
<p>OrbStack is gone. PostgreSQL stays. One fewer always-on runtime for this Mac.</p>
]]></content:encoded>
      
    </item>
    
  </channel>
</rss>
