<?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: kaz-builds-staff</title>
    <description>The latest articles on DEV Community by kaz-builds-staff (@kaz-builds-staff).</description>
    <link>https://dev.to/kaz-builds-staff</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%2F3869247%2F085124ee-f7c2-4fc0-8d43-fc7bb24c68ea.png</url>
      <title>DEV Community: kaz-builds-staff</title>
      <link>https://dev.to/kaz-builds-staff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaz-builds-staff"/>
    <language>en</language>
    <item>
      <title>11 Failures Before My CI/CD Pipeline Worked: A VibeCoder's Guide to Expo + GitHub Actions + DeployGate</title>
      <dc:creator>kaz-builds-staff</dc:creator>
      <pubDate>Fri, 10 Apr 2026 08:34:37 +0000</pubDate>
      <link>https://dev.to/kaz-builds-staff/11-failures-before-my-cicd-pipeline-worked-a-vibecoders-guide-to-expo-github-actions--2gao</link>
      <guid>https://dev.to/kaz-builds-staff/11-failures-before-my-cicd-pipeline-worked-a-vibecoders-guide-to-expo-github-actions--2gao</guid>
      <description>&lt;p&gt;I can't write code.&lt;/p&gt;

&lt;p&gt;That's not quite right. I describe what I want, Claude writes the code, and I focus on what to build. I'm what people call a &lt;strong&gt;VibeCoder&lt;/strong&gt; — someone who builds products with AI, spending their time on product decisions rather than syntax.&lt;/p&gt;

&lt;p&gt;I'm building "Arc," a mobile CRM for bodywork therapists, using Expo, React Native, and Supabase. The MVP is 85% done — 28 files, 27 screens, 18 of them functional.&lt;/p&gt;

&lt;p&gt;The problem wasn't building it. The problem was &lt;strong&gt;getting it onto a tester's phone&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The last mile is the hardest mile
&lt;/h2&gt;

&lt;p&gt;Writing code is fast. Tell Claude "build this screen" and something functional appears in minutes.&lt;/p&gt;

&lt;p&gt;But putting that build on a tester's iPhone requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building with Xcode&lt;/li&gt;
&lt;li&gt;Generating an .ipa file&lt;/li&gt;
&lt;li&gt;Uploading it somewhere&lt;/li&gt;
&lt;li&gt;Telling testers "there's a new version"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a VibeCoder, this "last mile" is the biggest bottleneck — not writing code, but delivering what you've written.&lt;/p&gt;

&lt;p&gt;TestFlight? Apple's review queue adds hours or days. Expo Go? Can't use native modules. EAS Build (cloud)? Monthly subscription for a solo dev feels wrong.&lt;/p&gt;

&lt;p&gt;The answer turned out to be &lt;strong&gt;DeployGate + a self-hosted GitHub Actions runner on my 13-inch M5 MacBook Air&lt;/strong&gt;. Push to &lt;code&gt;main&lt;/code&gt;, wait ~10 minutes, and every tester gets the latest build at a fixed URL. $0/month. Unlimited builds.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I wanted
&lt;/h2&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%2Fenp95win1nceeeiw5021.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%2Fenp95win1nceeeiw5021.png" alt=" " width="800" height="612"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Simple, right? Here's what actually happened — 11 failures before attempt #12 finally worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt #1 — Workflow collision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Build failed at 3:54 — crashed on "Embed Pods Frameworks."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Two workflows triggered on the same &lt;code&gt;push to main&lt;/code&gt; — one for DeployGate, one for TestFlight. My Mac's Xcode resources couldn't handle both simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Moved the TestFlight workflow out of the active directory.&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/workflows-disabled
&lt;span class="nb"&gt;mv&lt;/span&gt; .github/workflows/build-and-submit.yml .github/workflows-disabled/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Attempts #2–3 — Binary files in the repo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Every push uploaded 15MB of binary data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; A &lt;code&gt;.ipa&lt;/code&gt; file and an Apple certificate had been accidentally committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Added them to &lt;code&gt;.gitignore&lt;/code&gt; and purged from history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build-*.ipa
AppleWWDRCAG3.cer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lesson: &lt;strong&gt;build artifacts never belong in a repository.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempts #4–5 — Package drift
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Same "Embed Pods Frameworks" crash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Between sessions, &lt;code&gt;react-native&lt;/code&gt; had silently updated from 0.83.2 to 0.83.4, and &lt;code&gt;react-native-worklets&lt;/code&gt; appeared as a new peer dependency of &lt;code&gt;react-native-reanimated&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How I found it:&lt;/strong&gt; Checked out the last known working commit's &lt;code&gt;package.json&lt;/code&gt; and rebuilt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout 89fcbe9 &lt;span class="nt"&gt;--&lt;/span&gt; package.json package-lock.json
npm ci
eas build &lt;span class="nt"&gt;--platform&lt;/span&gt; ios &lt;span class="nt"&gt;--profile&lt;/span&gt; preview &lt;span class="nt"&gt;--local&lt;/span&gt;
&lt;span class="c"&gt;# → Build successful&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Pinned packages to the stable version, then formally added the missing dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempts #6–7 — Stale runner cache
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Builds pass locally, fail in CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The self-hosted runner's working directory had stale &lt;code&gt;node_modules&lt;/code&gt; and Xcode &lt;code&gt;DerivedData&lt;/code&gt; from previous runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Added a cache-clearing step to the workflow.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clean caches&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;rm -rf ~/Library/Developer/Xcode/DerivedData/Arc-*&lt;/span&gt;
    &lt;span class="s"&gt;rm -rf /tmp/eas-build-local-nodejs&lt;/span&gt;
    &lt;span class="s"&gt;rm -rf /tmp/eas-cli-nodejs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Attempts #8–9 — Credentials source ignored
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Console showed &lt;code&gt;Using remote iOS credentials (Expo server)&lt;/code&gt; when I wanted local credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The &lt;code&gt;EAS_CREDENTIALS_SOURCE=local&lt;/code&gt; environment variable was silently ignored by eas-cli 18.5.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Set it directly in &lt;code&gt;eas.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"internal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credentialsSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Attempt #10 — Missing credentials file
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; &lt;code&gt;credentials.json does not exist in the project root directory&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; &lt;code&gt;credentials.json&lt;/code&gt; is in &lt;code&gt;.gitignore&lt;/code&gt; (as it should be), so the runner's checkout didn't include it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Copied it from the Mac's home directory in the workflow.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Copy credentials&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;cp ~/client-crm/credentials.json ./credentials.json&lt;/span&gt;
    &lt;span class="s"&gt;cp -r ~/client-crm/credentials ./credentials&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Attempt #11 — Locked keychain
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Everything compiled fine. Failed only at the final code-signing step ("Embed Pods Frameworks").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The self-hosted runner runs as a LaunchAgent without access to the GUI session's keychain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Explicitly unlock the keychain in the workflow.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unlock keychain&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" ~/Library/Keychains/login.keychain-db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store your Mac login password in GitHub Secrets as &lt;code&gt;KEYCHAIN_PASSWORD&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt #12 — It worked 🎉
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✔ Using local iOS credentials (credentials.json)
✔ Archive Succeeded
✔ Successfully exported and signed the ipa file
✔ Uploading to DeployGate...
✔ Distribution page updated
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fqae9b9ncv8ml4atpgxkv.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%2Fqae9b9ncv8ml4atpgxkv.png" alt=" " width="800" height="584"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;From &lt;code&gt;git push&lt;/code&gt; to a tester opening the build on their phone: about 10 minutes.&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%2Fnx8mgiq13b47flk2cpwn.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%2Fnx8mgiq13b47flk2cpwn.png" alt=" " width="800" height="1329"&gt;&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  The final workflow
&lt;/h2&gt;

&lt;p&gt;Here's the complete GitHub Actions workflow that survived all 11 failures:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy to DeployGate&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clean caches&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;rm -rf ~/Library/Developer/Xcode/DerivedData/Arc-*&lt;/span&gt;
          &lt;span class="s"&gt;rm -rf /tmp/eas-build-local-nodejs&lt;/span&gt;
          &lt;span class="s"&gt;rm -rf /tmp/eas-cli-nodejs&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Copy credentials&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cp ~/client-crm/credentials.json ./credentials.json&lt;/span&gt;
          &lt;span class="s"&gt;cp -r ~/client-crm/credentials ./credentials&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unlock keychain&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" ~/Library/Keychains/login.keychain-db&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build .ipa&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eas build --platform ios --profile preview --local --non-interactive&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to DeployGate &amp;amp; update distribution page&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;IPA_FILE=$(ls build-*.ipa | head -1)&lt;/span&gt;
          &lt;span class="s"&gt;curl -X POST \&lt;/span&gt;
            &lt;span class="s"&gt;"https://deploygate.com/api/users/${DEPLOYGATE_OWNER}/apps" \&lt;/span&gt;
            &lt;span class="s"&gt;-H "Authorization: Bearer ${DEPLOYGATE_API_TOKEN}" \&lt;/span&gt;
            &lt;span class="s"&gt;-F "file=@${IPA_FILE}" \&lt;/span&gt;
            &lt;span class="s"&gt;--form-string "distribution_key=${DEPLOYGATE_DIST_KEY}" \&lt;/span&gt;
            &lt;span class="s"&gt;--form-string "release_note=$(git log -1 --pretty=%B)"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DEPLOYGATE_OWNER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEPLOYGATE_OWNER }}&lt;/span&gt;
          &lt;span class="na"&gt;DEPLOYGATE_API_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEPLOYGATE_API_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;DEPLOYGATE_DIST_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEPLOYGATE_DIST_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DeployGate upload API accepts both the &lt;code&gt;.ipa&lt;/code&gt; file and distribution page parameters in a single call — pass &lt;code&gt;distribution_key&lt;/code&gt; to update an existing distribution page and &lt;code&gt;release_note&lt;/code&gt; to auto-populate release notes from your commit message. Testers access the same fixed URL every time. No new links to send, no app store to check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why DeployGate instead of alternatives?
&lt;/h2&gt;

&lt;p&gt;I tried the obvious options first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TestFlight:&lt;/strong&gt; Apple's review queue for the first build of each version can take hours to days. When you're iterating multiple times per day, that's a dealbreaker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Build (cloud):&lt;/strong&gt; Works great, but the monthly cost adds up for a solo developer building across multiple projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Go:&lt;/strong&gt; Fine for early prototyping, but the moment you add native modules (and you will), it stops working.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DeployGate fit because: no review queue, free tier for indie devs, and an API simple enough to add to a GitHub Actions workflow in one &lt;code&gt;curl&lt;/code&gt; command. Testers don't need accounts — they open a link and install.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;After 11 failures, every lesson is burned in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One workflow per trigger.&lt;/strong&gt; Two workflows on the same &lt;code&gt;push&lt;/code&gt; with shared Xcode resources will collide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin your packages.&lt;/strong&gt; &lt;code&gt;npm ci&lt;/code&gt; only protects you if &lt;code&gt;package-lock.json&lt;/code&gt; hasn't drifted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted runners need manual cache hygiene.&lt;/strong&gt; Unlike cloud runners, they accumulate state between builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eas.json&lt;/code&gt; over environment variables.&lt;/strong&gt; Some CLI tools silently ignore env vars for credential configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keychain access is not automatic.&lt;/strong&gt; LaunchAgent processes can't see your GUI keychain without explicit unlock.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep credentials out of the repo, but script their placement.&lt;/strong&gt; Copy them from a known location on the build machine.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;I'm a solo developer building multiple products — a CRM for bodywork therapists, a learning tool for structural consultants, and a few things I can't talk about yet.&lt;/p&gt;

&lt;p&gt;Making mistakes along the way and sharing all of it here. Follow &lt;a href="https://x.com/kazbuildsstaff" rel="noopener noreferrer"&gt;@kazbuildsstaff&lt;/a&gt; for real build-in-public, not the highlight reel.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>mobile</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>I Cut My Cold Start by 82% Without Changing a Single Line of Code</title>
      <dc:creator>kaz-builds-staff</dc:creator>
      <pubDate>Fri, 10 Apr 2026 02:46:33 +0000</pubDate>
      <link>https://dev.to/kaz-builds-staff/i-cut-my-cold-start-by-82-without-changing-a-single-line-of-code-3gm2</link>
      <guid>https://dev.to/kaz-builds-staff/i-cut-my-cold-start-by-82-without-changing-a-single-line-of-code-3gm2</guid>
      <description>&lt;p&gt;I was setting up Claude connectors for my Supabase project — checking permissions, tightening auth scopes — when I noticed something.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Region: South Asia (Mumbai)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Wait. I'm in Tokyo. All my beta testers are in Japan.&lt;br&gt;
I was sure I had picked Tokyo when I created this project. But the dropdown said otherwise. Probably got pulled toward India because I had another India-related project on my mind at the time. One wrong click, months ago, that I never looked at again.&lt;/p&gt;

&lt;p&gt;And it explained everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Slow First Load That Wouldn't Go Away
&lt;/h2&gt;

&lt;p&gt;For weeks, my app's cold start had been bugging me. Not enough to stop feature development — I was building in parallel across multiple products — but enough to notice every single time I opened the app.&lt;br&gt;
I'd already tried the right things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimistic UI to decouple data fetching from rendering&lt;/li&gt;
&lt;li&gt;Skeleton screens to improve perceived load time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These helped for navigating between pages. But that first load — opening the app, hitting login — always felt sluggish. I assumed it was a Next.js SSR issue or a Supabase query that needed optimization.&lt;/p&gt;

&lt;p&gt;It wasn't. The database was just in the wrong country.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Tool That Wouldn't Migrate
&lt;/h2&gt;

&lt;p&gt;No problem, I thought. Supabase has a project migration tool. I'll just move it to Tokyo.&lt;/p&gt;

&lt;p&gt;Found the "Restore to new project" button in the dashboard. Me and my AI pair programmer literally celebrated.&lt;/p&gt;

&lt;p&gt;Clicked it. Read the modal:&lt;/p&gt;

&lt;p&gt;"Project region will stay the same: &lt;code&gt;ap-south-1&lt;/code&gt;"&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%2Ffmi7dtb5pg78m5phuuqr.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%2Ffmi7dtb5pg78m5phuuqr.png" alt=" " width="800" height="1212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The one tool designed for this exact situation... only works within the same region.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going the Hard Way
&lt;/h2&gt;

&lt;p&gt;So I went manual.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pg_dump&lt;/code&gt; → GitHub Actions → new Tokyo project.&lt;br&gt;
First wall: &lt;code&gt;pg_dump&lt;/code&gt; version 16 can't dump a Postgres 17 database. No useful error message. Just a silent failure in CI. Had to manually install &lt;code&gt;postgresql-client-17&lt;/code&gt; in the workflow.&lt;/p&gt;

&lt;p&gt;Data migrated. Done?&lt;/p&gt;

&lt;p&gt;Not even close. The migration docs had warned me — I just didn't appreciate what "manual reconfiguration" actually meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google OAuth redirect URLs → all pointing to the old project&lt;/li&gt;
&lt;li&gt;Every Vercel environment variable → old project URL and keys&lt;/li&gt;
&lt;li&gt;Supabase anon key, secret key, project URL in &lt;code&gt;.env&lt;/code&gt; → all old&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was basically rewiring the entire authentication system by hand.&lt;/p&gt;

&lt;p&gt;And while I was knee-deep in this, Vercel builds started failing from a completely unrelated bug &lt;code&gt;(Date.now()&lt;/code&gt; called during render). Two simultaneous failures. Your brain merges them into one impossible problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was It Worth It?
&lt;/h2&gt;

&lt;p&gt;I measured latency before and after. Here are the honest numbers:&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%2F9uxhpy1cx0gk8w84n487.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%2F9uxhpy1cx0gk8w84n487.png" alt=" " width="800" height="508"&gt;&lt;/a&gt;&lt;br&gt;
Median latency: nearly identical. ~40ms from both regions. Supabase's CDN does an impressive job absorbing the geographic difference for cached requests.&lt;br&gt;
But the cold start — that first request when your app wakes up?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;367ms → 64ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's &lt;strong&gt;82% faster&lt;/strong&gt;. And that's the moment your users actually feel. The login screen. The first dashboard load. The moment they decide if your app is "fast" or "slow."&lt;/p&gt;

&lt;p&gt;All those optimistic UI improvements I'd built? They were masking a structural problem. The foundation was off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Things I'd Tell Anyone Starting a Supabase Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Region selection is a one-way door. Pick the region closest to your first users — not your future ones, not the project that's on your mind that day.&lt;/li&gt;
&lt;li&gt;Supabase's built-in migration = same region only. Cross-region means &lt;code&gt;pg_dump&lt;/code&gt;, manual auth rewiring, and a full env var rebuild. Budget a day, not an hour.&lt;/li&gt;
&lt;li&gt;Measure your latency before you migrate. Once you switch, that "before" data is gone forever.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;I'm building multiple products right now — a structural consulting SaaS, a management dashboard for a wellness business, and some things I can't talk about yet.&lt;/p&gt;

&lt;p&gt;Making mistakes along the way and sharing all of it here. Follow &lt;a href="https://x.com/kazbuildsstaff" rel="noopener noreferrer"&gt;@kazbuildsstaff&lt;/a&gt; if you want real build-in-public, not the highlight reel.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>performance</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
