<?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: Jackson</title>
    <description>The latest articles on DEV Community by Jackson (@maclessdev).</description>
    <link>https://dev.to/maclessdev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4079628%2F4c7c384d-c201-47fa-bdc3-887f5b74d44f.png</url>
      <title>DEV Community: Jackson</title>
      <link>https://dev.to/maclessdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maclessdev"/>
    <language>en</language>
    <item>
      <title>Debugging "Exporting for App Store Distribution failed"</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sun, 30 Aug 2026 03:06:49 +0000</pubDate>
      <link>https://dev.to/maclessdev/debugging-exporting-for-app-store-distribution-failed-1ajd</link>
      <guid>https://dev.to/maclessdev/debugging-exporting-for-app-store-distribution-failed-1ajd</guid>
      <description>&lt;p&gt;If you've hit this exact banner, you already know it tells you almost nothing. "Exporting for App Store Distribution failed. Please download the logs artifact for more information" is the real wording from a live Apple Developer Forums thread, and that second sentence is the important one: the banner is a wrapper, not a diagnosis. Underneath it, Xcode is really just reporting that &lt;code&gt;xcodebuild -exportArchive&lt;/code&gt; exited with a non-zero status, most commonly exit code 70. What actually caused that exit code is a completely different question every time, and it's sitting in the export logs, not in the banner text.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this error actually is
&lt;/h2&gt;

&lt;p&gt;Xcode Cloud shows this exact banner, and Xcode's own Distribute App flow in Organizer shows a version of the same thing, whenever the archive-to-.ipa export step fails for any underlying reason. It's the export equivalent of a generic "something went wrong" — genuinely useful only as a signal to go open the logs, not as a description of the problem itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real causes, pulled from real threads
&lt;/h2&gt;

&lt;p&gt;These aren't guesses. Each of these is a genuinely diagnosed case from Apple's own Developer Forums or fastlane's public issue tracker, not a paraphrase of "common wisdom."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No valid distribution certificate.&lt;/strong&gt; The export fails with "No signing certificate 'iOS Distribution' found" when the cert Xcode expects is missing, expired, or revoked. In one Xcode Cloud case, the fix was revoking the Xcode-Cloud-managed "Distribution Managed" certificate to force it to regenerate a valid one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transient Apple backend failures.&lt;/strong&gt; Sometimes the underlying error really is "Communication with Apple failed" (a 502) or a backend authentication-context bug, with nothing wrong in your project at all. There's no project-side fix for these — they resolve when Apple's own systems recover, or need an actual support ticket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An entitlement that doesn't match the provisioning profile.&lt;/strong&gt; One real case traced back to a typo in an entitlement key, which made the profile fail Apple's qualification check on export even though everything looked fine in Xcode's UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A shadowed system tool in your build environment.&lt;/strong&gt; Homebrew's &lt;code&gt;rsync&lt;/code&gt; can shadow macOS's own &lt;code&gt;rsync&lt;/code&gt; in &lt;code&gt;PATH&lt;/code&gt;, breaking the file copy step during export with the same generic exit-70 failure — a build-environment bug that has nothing to do with signing at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An Xcode version regression.&lt;/strong&gt; Xcode 26.2 shipped an invalid App Store Connect API parameter for projects with a watchOS target, which broke automatic signing during export. Apple confirmed it as a real bug; the workaround was reverting to Xcode 26.1 until it's fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed recently
&lt;/h2&gt;

&lt;p&gt;Two dated platform changes are worth knowing about, even though neither has a confirmed direct link to this specific error banner — they're real, recent changes to the surrounding signing and distribution landscape:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy manifests, enforced since May 1, 2024.&lt;/strong&gt; Newly added third-party SDKs on Apple's commonly-used-SDK list now require a privacy manifest. This is a separate, earlier gate than the export step, but it's part of the same broader "your binary has to check out before Apple will accept it" surface that's expanded in the last two years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Xcode 26.2's export regression, August 2026.&lt;/strong&gt; The watchOS-target signing bug above is dated and Apple-acknowledged (filed as FB21322904 and FB21627607) — worth checking if you're on that exact Xcode version and your project has a watch target.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually helps
&lt;/h2&gt;

&lt;p&gt;The single most useful habit here is treating the banner as a prompt to go looking, not as the answer itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open the actual export logs, not the summary banner.&lt;/strong&gt; Xcode Cloud's logs artifact and Xcode Organizer's own distribution logs both contain the real underlying error. The banner text is identical across every one of the causes above — the logs are where they stop looking identical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify your certificate and profile directly in the portals.&lt;/strong&gt; Don't trust what Xcode's local cache shows. Check the actual expiration and status of your distribution certificate and provisioning profile in Apple Developer and App Store Connect, since a locally-cached profile can look fine in Xcode while being invalid server-side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check every extension target has its own App ID and profile.&lt;/strong&gt; Widgets, share extensions, and Safari Web Extensions each need their own explicit App ID and provisioning profile — a mismatch or missing profile on just the extension target, while the main app target is fine, is a real and recurring cause of export failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean derived data and re-download profiles before assuming it's a code problem.&lt;/strong&gt; A stale local cache of a since-revoked or since-renewed certificate or profile causes failures that have nothing to do with your actual project configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If nothing project-side is wrong, consider that it might just be Apple's backend.&lt;/strong&gt; Some of these failures are genuinely transient server-side issues. If the logs point at a communication or authentication error rather than a signing mismatch, retrying later is a legitimate first move, not giving up.&lt;/p&gt;

&lt;p&gt;The pattern underneath all of this: the banner text is the same no matter which of these five things actually happened. Every real fix starts with going past it into the logs, not with guessing based on the banner alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  I built a free tool for this
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://macless.dev/signing-doctor.html" rel="noopener noreferrer"&gt;Signing Doctor&lt;/a&gt; (free) specifically because this class of error is so generic on its face and so specific underneath. Paste in your actual error output and it helps you figure out which of the real underlying causes you're actually looking at, rather than guessing from the banner text alone. It's part of the same project as &lt;a href="https://macless.dev/#pricing" rel="noopener noreferrer"&gt;Macless&lt;/a&gt;, which handles the signing and CI side of shipping an iOS app to TestFlight without a Mac.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>xcode</category>
      <category>debugging</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>The most common reasons Apple rejects your app</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sun, 30 Aug 2026 00:00:09 +0000</pubDate>
      <link>https://dev.to/maclessdev/the-most-common-reasons-apple-rejects-your-app-20gl</link>
      <guid>https://dev.to/maclessdev/the-most-common-reasons-apple-rejects-your-app-20gl</guid>
      <description>&lt;p&gt;Getting a rejection email from App Review feels personal. It usually isn't. Apple runs the same review process against every submission, and most rejections trace back to a small number of guidelines that come up again and again. Knowing which ones, and what they actually mean, turns a vague rejection into a fixable checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  How often this actually happens
&lt;/h2&gt;

&lt;p&gt;Apple's own 2024 App Store Transparency Report puts real numbers on this. Out of 7.77 million app submissions reviewed that year, 1.93 million were rejected, roughly 25%. Of those, 295,109 were fixed and approved on resubmission. Separately, 82,509 already-live apps were removed after the fact, most commonly for guideline or design violations (42,252), followed by fraud (38,315).&lt;/p&gt;

&lt;p&gt;Apple hasn't published a breakdown of rejections by specific guideline number, so treat any listicle claiming "62% of rejections are X" as unsourced. What Apple has said, in its own commentary alongside the report, is that the most common drivers, in order, are performance and bugs, legal issues, design problems, business-model (payment) violations, and safety risks. That ordering lines up with the specific guidelines below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guidelines that actually catch people
&lt;/h2&gt;

&lt;p&gt;These are pulled directly from Apple's current App Store Review Guidelines, not paraphrased from a third party.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guideline 2.1, App Completeness.&lt;/strong&gt; Covers crashes, obvious bugs, placeholder content, broken demo accounts, and non-functional in-app purchases. If a reviewer can't get past your login screen or your app crashes on launch, this is the line it gets cited under. It's the single most avoidable category, because it's the one you can actually test yourself before submitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guideline 4.2, Minimum Functionality.&lt;/strong&gt; Your app has to be more than "a repackaged website." Apple wants "lasting entertainment value or adequate utility." A thin wrapper around a web view, with no native functionality added, gets flagged here. A sub-clause, 4.2.6, specifically targets apps built from commercialized app-generator templates unless the actual content provider is the one submitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guideline 4.3, Spam.&lt;/strong&gt; 4.3(a) bans duplicate submissions under different Bundle IDs, the classic example being the same app re-skinned per city. 4.3(b) targets near-clones in saturated categories (flashlights, simple timers, wallpaper apps, fortune-telling apps) unless yours is "meaningfully different" from what's already in the store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guideline 5.1.1, Data Collection and Storage.&lt;/strong&gt; Requires a working privacy policy link, explicit consent before collecting any data (including "anonymous" analytics), no forcing or tricking users into granting permissions they don't need, and account deletion if your app lets people create an account in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guideline 3.1.1, In-App Purchase.&lt;/strong&gt; Any unlock of digital features or content inside your app has to go through Apple's own In-App Purchase system. License keys, QR codes, or crypto-based workarounds to unlock paid features get rejected here, and loot-box mechanics specifically require published odds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed recently
&lt;/h2&gt;

&lt;p&gt;Two real, dated changes worth knowing about if you haven't submitted in a while:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy manifests, enforced since May 1, 2024.&lt;/strong&gt; Newly added third-party SDKs on Apple's commonly-used-SDK list now require a privacy manifest and, in many cases, a valid signature. An app missing one for a flagged SDK gets rejected before a human reviewer even looks at it. Apple has signaled this requirement will keep expanding to cover more of the binary, not less.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guideline 1.2 now explicitly covers random or anonymous chat, as of February 2026.&lt;/strong&gt; If your app includes any kind of stranger-matching chat feature, Apple's guidelines were revised to state plainly that this falls under User-Generated Content rules, meaning you need real moderation in place, not just a report button as an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually helps
&lt;/h2&gt;

&lt;p&gt;None of this is exotic. Most of it is just discipline about what you check before you hit submit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the actual rejection line, not just the guideline number.&lt;/strong&gt; App Review's message usually tells you specifically what triggered it, a missing purpose string, a specific screen that crashed, a mismatched screenshot. The guideline number is the category, not the diagnosis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give reviewers a working demo account.&lt;/strong&gt; If your app requires login, App Review needs a real account that actually works, with real data behind it, not a stub. A broken demo account is one of the most common and entirely self-inflicted 2.1 rejections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't build a payment workaround.&lt;/strong&gt; If a feature unlocks with money, it has to go through Apple's In-App Purchase system. This is 3.1.1 and Apple checks for it specifically, including license keys and external payment links for digital content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your privacy manifest before you add a new SDK, not after a rejection.&lt;/strong&gt; If you're pulling in a new third-party dependency, check whether it's on Apple's commonly-used-SDK list and whether it ships a manifest. Finding this out from a rejection costs you a review cycle you didn't need to spend.&lt;/p&gt;

&lt;p&gt;The pattern underneath all five categories: Apple is checking whether your app does what it claims, respects the data it touches, and pays through the channel it's supposed to. Most rejections are one of those three things, stated in guideline language instead of plain English.&lt;/p&gt;

&lt;h2&gt;
  
  
  I built a free tool for this
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://macless.dev/rejection-doctor.html" rel="noopener noreferrer"&gt;Rejection Doctor&lt;/a&gt; (free) after seeing how often the same handful of guidelines come up. Paste in your actual rejection message and it matches it against 57 real rejection categories, tells you what's actually going on, and can draft an appeal letter if you think the rejection was a mistake. It's part of the same project as &lt;a href="https://macless.dev/#pricing" rel="noopener noreferrer"&gt;Macless&lt;/a&gt;, which handles the signing and CI side of shipping an iOS app without a Mac.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>mobiledev</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>The iOS certificate problem nobody has actually solved</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sat, 29 Aug 2026 21:38:16 +0000</pubDate>
      <link>https://dev.to/maclessdev/the-ios-certificate-problem-nobody-has-actually-solved-1gff</link>
      <guid>https://dev.to/maclessdev/the-ios-certificate-problem-nobody-has-actually-solved-1gff</guid>
      <description>&lt;p&gt;If you have ever set up iOS code signing for a CI pipeline, you already know the feeling. Everything works for months, then one day a build fails with a certificate error, and you spend the next two hours relearning a system you thought you understood.&lt;/p&gt;

&lt;p&gt;I want to write about why that keeps happening, because it is not a "you set it up wrong" problem. It is a problem the most popular tool in this space has openly said it has not solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The admission
&lt;/h2&gt;

&lt;p&gt;fastlane is the standard way most teams manage iOS signing. It is free, open source, and has been around since 2015. Its own maintainers have said, on a public GitHub discussion, that certificate lifecycle management is one of the few things fastlane still has not figured out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The problem of expiring certificates to me is one of the few fundamental signing features that fastlane has not yet solved... we all face again and again... I really hope we can put this issue to bed soon!"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is not a random forum comment. That is a maintainer, on the project's own GitHub, describing a problem that keeps coming back for everyone who uses the tool. If you search fastlane's issue tracker, you will find real, specific reports of what that looks like in practice. One developer described &lt;code&gt;fastlane match&lt;/code&gt; hanging indefinitely on "Installing provisioning profile," and said outright: "I accidentally tried waiting for 2.5 hours." Another reported an expired WWDR intermediate certificate silently breaking their local keychain with no clear error pointing at the actual cause.&lt;/p&gt;

&lt;p&gt;And when match genuinely gets stuck, the documented recovery path is &lt;code&gt;match nuke&lt;/code&gt;, which deletes every certificate and profile of the affected type for your entire team, not just yours. One developer's reaction to that in the same thread sums it up: "does nuking remove all certificates &amp;amp; profiles... including other people's... in your iOS team?" Yes. It does.&lt;/p&gt;

&lt;p&gt;I am not writing this to pick on fastlane. It is a genuinely useful tool and a lot of serious iOS shops run on it successfully. I am writing this because if the tool built specifically to solve this problem says the problem is not solved, that tells you something real about how the underlying system works, not about who is using it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the certificates themselves are the trap
&lt;/h2&gt;

&lt;p&gt;Apple caps how many active Distribution certificates you can have on an account. In practice this is usually two or three. That sounds generous until you see what actually exhausts it.&lt;/p&gt;

&lt;p&gt;If your CI pipeline is set up to auto-provision (generate a fresh certificate on every run instead of reusing one you already created), you will hit that cap surprisingly fast. Bitrise's own documentation for their automatic provisioning steps says this plainly: without a certificate you pre-uploaded yourself, "steps with automatic provisioning will generate one on the fly every time." Run that pipeline a few dozen times and you can burn through your entire certificate allowance without ever manually creating one.&lt;/p&gt;

&lt;p&gt;When you do hit the cap, the error from Apple's own API is not exactly friendly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Could not create another Distribution certificate,
reached the maximum number of available Distribution certificates."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That message tells you what happened. It does not tell you which of your existing certificates is safe to revoke, or which apps depend on which certificate. If you have more than one app signed with certificates from the same account, you now have to go figure that out manually before you can unblock yourself, usually while a release is waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The errors that are not your fault
&lt;/h2&gt;

&lt;p&gt;A few other things I have hit, or seen well documented elsewhere, that are worth knowing before they happen to you at 11pm the night before a submission:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A bare 500 on profile creation.&lt;/strong&gt; &lt;code&gt;UNEXPECTED_ERROR&lt;/code&gt; on a &lt;code&gt;POST&lt;/code&gt; to the provisioning profiles endpoint is a real, recurring error reported directly on Apple's own developer forums. Sometimes it resolves on a plain retry with zero changes to your config. If you see this, try again before you start second-guessing your setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The wrong kind of API key.&lt;/strong&gt; App Store Connect API keys come in two flavors: Individual and Team. Provisioning-related endpoints specifically require a Team key. If you are using an Individual key, you will get a 403 on exactly those endpoints while everything else you try with that same key works fine, which makes it a lot more confusing to diagnose than a blanket permission error would be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enterprise vs. App Store account type is invisible to the API.&lt;/strong&gt; Apple's own documentation admits the API cannot automatically tell these two account types apart, so if you are generating certificates programmatically for an Enterprise (in-house distribution) account, you need to set the &lt;code&gt;in_house&lt;/code&gt; flag manually. Nothing will warn you if you get this wrong until later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple can change the API out from under you with no warning.&lt;/strong&gt; In 2025, Apple silently removed an undocumented parameter, &lt;code&gt;templateName&lt;/code&gt;, from the App Store Connect API. That parameter was load-bearing for fastlane's automated provisioning-profile creation, and its removal broke automated signing industry-wide with no migration path and no changelog entry anyone could point to in advance. There is a real postmortem about it if you want the full story (search "Sourcetoad Apple templateName fastlane"), and the related GitHub issue was still open the last time I checked. This is the part that is genuinely hard to defend against: the tool can be correct today and wrong next month because Apple changed something nobody announced.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually helps
&lt;/h2&gt;

&lt;p&gt;None of this means give up and do everything by hand. A few things that make a real, measurable difference:&lt;/p&gt;

&lt;p&gt;Check your certificate count before you create a new one, not after you get the error. A simple &lt;code&gt;GET&lt;/code&gt; on the certificates endpoint tells you exactly how many of your two or three slots are already in use, so you can decide whether to reuse one or revoke an old one before you are blocked mid-release.&lt;/p&gt;

&lt;p&gt;Never auto-provision on every CI run. Generate your signing certificate and provisioning profile once, store them (encrypted) somewhere your pipeline can reach, and reuse them. This alone avoids most of the cap-exhaustion problem entirely.&lt;/p&gt;

&lt;p&gt;Build retry logic around the specific error codes above, not a generic catch-all. A 500 on profile creation deserves a retry with backoff. A 403 on provisioning specifically, when your key works everywhere else, deserves a message that says "check whether this is a Team key," not "permission denied."&lt;/p&gt;

&lt;p&gt;Keep a place to watch for expiry before it becomes an outage. A scheduled check that looks at your certificate and profile expiration dates and pings you a few weeks out is a small thing to build and it turns a surprise failure into a calendar reminder.&lt;/p&gt;

&lt;p&gt;I ended up building most of the last two points directly into my own pipeline (Macless, macless.dev), because I kept hitting exactly this set of problems while shipping my own app without owning a Mac, and got tired of relearning the same lessons every few months. If you are dealing with this on your own setup right now, I hope at least the specific error messages above save you the hour I spent figuring out what they meant the first time.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>githubactions</category>
      <category>mobile</category>
      <category>cicd</category>
    </item>
    <item>
      <title>I hosted the no-Mac iOS pipeline</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Thu, 27 Aug 2026 05:01:48 +0000</pubDate>
      <link>https://dev.to/maclessdev/i-hosted-the-no-mac-ios-pipeline-its-1-today-53i2</link>
      <guid>https://dev.to/maclessdev/i-hosted-the-no-mac-ios-pipeline-its-1-today-53i2</guid>
      <description>&lt;p&gt;A bit ago I wrote about the GitHub Actions setup I built to ship Citolex to the App Store without owning a Mac. That part hasn't really changed. What changed is how you actually set it up.&lt;/p&gt;

&lt;p&gt;Before this, you'd download a template and run a script yourself. Works fine, but it's still a terminal and a bunch of manual steps between paying and actually having something running.&lt;/p&gt;

&lt;p&gt;So I hosted it. You pay, log into GitHub, pick or make a repo, and a wizard pushes the whole pipeline into it for you using GitHub's API, you don't touch git at all. Then it walks you through your signing secrets and actually tells you what's wrong with a cert or profile before you waste a build on it, not after.&lt;/p&gt;

&lt;p&gt;The signing check part runs as a Cloudflare Worker, no Mac or VM behind it. Turns out provisioning profiles and certs are just regular crypto formats, Web Crypto can read them fine on its own.&lt;/p&gt;

&lt;p&gt;It's $19.99/month, or $299 once and nothing after — iOS and Android both. If you've fought with iOS signing in CI before, I'd like your eyes on it.&lt;/p&gt;

&lt;p&gt;I built this, it's my thing. macless.dev&lt;/p&gt;

</description>
      <category>ios</category>
      <category>githubactions</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Citolex is live on the App Store, built without owning a Mac</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Mon, 24 Aug 2026 22:40:03 +0000</pubDate>
      <link>https://dev.to/maclessdev/citolex-is-live-on-the-app-store-built-without-owning-a-mac-39fi</link>
      <guid>https://dev.to/maclessdev/citolex-is-live-on-the-app-store-built-without-owning-a-mac-39fi</guid>
      <description>&lt;p&gt;Citolex just went live on the App Store. It's a speed-reading app, flash-based word-by-word reading with synced text-to-speech, everything on-device.&lt;/p&gt;

&lt;p&gt;The part I actually want to write about is how it got built and shipped. I don't own a Mac. Xcode needs one to sign and archive an iOS build, so instead of buying hardware I built a GitHub Actions pipeline that does it for me.&lt;/p&gt;

&lt;p&gt;Capacitor wraps the web app (HTML/JS/CSS) in a native iOS shell, so most of the UI didn't need native code. But Citolex needed a few things Capacitor can't reach on its own: text-to-speech, a Share Extension so you can send text into the app from Safari or Notes, an App Group so the extension and the main app can share data, and background audio so narration keeps playing when the app isn't in the foreground. Those are native Swift plugins layered on top of the Capacitor shell.&lt;/p&gt;

&lt;p&gt;The workflow runs on GitHub's free macOS build minutes (available on public repos), checks out the code, pulls signing certs and provisioning profiles from GitHub secrets, archives with xcodebuild, exports a signed ipa, and uploads to App Store Connect on a normal git push.&lt;/p&gt;

&lt;p&gt;What actually took the time wasn't the workflow file. It was the signing debug loop: certificate and profile mismatches, needing the exact same name between the Apple Developer portal and the CI config, and TestFlight silently rejecting a reupload with an unchanged build number while the CI log stayed green the whole time with no useful error. Eventually I added a validate step that runs before the real build and checks the Team ID and bundle ID actually match what's in the provisioning profile, so a mismatch fails fast with a specific message instead of a cryptic xcodebuild error twenty minutes in.&lt;/p&gt;

&lt;p&gt;It shipped. Citolex is live, built and signed entirely through that pipeline, no Mac at any point: apps.apple.com/us/app/citolex/id6800933053&lt;/p&gt;

&lt;p&gt;If you're in the same position, no Mac, need to ship a real iOS app, that pipeline is now packaged up as Macless (macless.dev).&lt;/p&gt;

</description>
      <category>ios</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Orchard Cloud's Pricing Philosophy, Explained</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sun, 23 Aug 2026 17:37:08 +0000</pubDate>
      <link>https://dev.to/maclessdev/orchard-clouds-pricing-philosophy-explained-5cel</link>
      <guid>https://dev.to/maclessdev/orchard-clouds-pricing-philosophy-explained-5cel</guid>
      <description>&lt;p&gt;A lot of people ask us why Orchard Cloud costs what it costs. Fair question. Here's the honest answer.&lt;/p&gt;

&lt;p&gt;We looked at what enterprise cloud compute costs, we looked at what enterprise device leasing costs, and then we asked what it would take to combine the two into a single line item a finance team could approve without too many follow-up questions.&lt;/p&gt;

&lt;p&gt;Starter exists mostly so people have something to compare Growth to. This is a well-understood pattern and we're not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;Enterprise is "Contact us" because at that volume we genuinely do need to have a conversation, mostly about logistics.&lt;/p&gt;

&lt;p&gt;Onboarding takes 12 to 16 months. We used to say 6 to 8 weeks. We've since gotten more experience with what onboarding actually involves, and the current estimate reflects that.&lt;/p&gt;

&lt;p&gt;If any of this makes sense to you, &lt;a href="https://orchard.macless.dev" rel="noopener noreferrer"&gt;orchard.macless.dev&lt;/a&gt; has a calculator.&lt;/p&gt;

</description>
      <category>satire</category>
      <category>startup</category>
    </item>
    <item>
      <title>How We Built Orchard Cloud's Enterprise Mac Provisioning Platform</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sun, 23 Aug 2026 16:09:44 +0000</pubDate>
      <link>https://dev.to/maclessdev/how-we-built-orchard-clouds-enterprise-mac-provisioning-platform-1c3n</link>
      <guid>https://dev.to/maclessdev/how-we-built-orchard-clouds-enterprise-mac-provisioning-platform-1c3n</guid>
      <description>&lt;p&gt;At Orchard Cloud, we give engineering orgs elastic, on-demand access to fruit-branded computers they'll never personally own. Here's a look at how the platform works under the hood.&lt;/p&gt;

&lt;p&gt;Provisioning runs through a queue, evaluated against fleet availability and priority tier. Once approved, you get remote access to a Mac located somewhere in our fleet — we don't say where, for reasons of security.&lt;/p&gt;

&lt;p&gt;We also built a Total Cost of Mac Rental calculator so customers can see exactly what indefinite rental costs against just buying a Mac outright. It's the most honest part of the platform, which should tell you something.&lt;/p&gt;

&lt;p&gt;Full pricing, onboarding timelines, and the provisioning flow are live now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://orchard.macless.dev" rel="noopener noreferrer"&gt;https://orchard.macless.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>satire</category>
      <category>saas</category>
      <category>humor</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Most App Store Rejections Aren't About Your Code</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sun, 23 Aug 2026 14:23:41 +0000</pubDate>
      <link>https://dev.to/maclessdev/most-app-store-rejections-arent-about-your-code-3p75</link>
      <guid>https://dev.to/maclessdev/most-app-store-rejections-arent-about-your-code-3p75</guid>
      <description>&lt;p&gt;I've shipped enough iOS apps at this point to notice a pattern in App Store rejections. The app almost never gets rejected because something is broken. It gets rejected because of something in the metadata.&lt;/p&gt;

&lt;p&gt;Three examples I keep running into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing or vague privacy details.&lt;/strong&gt; Since iOS 17, Apple checks your PrivacyInfo.xcprivacy manifest against the actual APIs your app, and any third party SDKs, actually touch. If you use UserDefaults, a disk space check, or a system boot time call and haven't declared the matching required reason API category, review flags it. This has nothing to do with whether your app crashes. It's a paperwork mismatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A screenshot that doesn't match the app's actual UI.&lt;/strong&gt; Reviewers compare your submitted screenshots against what the build actually shows on first launch. Redesign a screen and forget to update one screenshot, and you get a Guideline 2.3.3 rejection with zero code changes needed, just new screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An unclear permission usage string.&lt;/strong&gt; "This app needs your location" gets rejected under Guideline 5.1.1 for being too vague. Apple wants the specific reason, something like "used to show nearby stores." Again, zero code involved, just a string in Info.plist.&lt;/p&gt;

&lt;p&gt;The pattern holds across all three. Read the specific line in the rejection message before assuming something's fundamentally broken. Apple's rejection emails usually name the exact guideline number and the exact issue. It's tempting to panic and start debugging your build. Most of the time the fix is a sentence of text or a missing manifest entry, not an afternoon in Xcode.&lt;/p&gt;

&lt;p&gt;I built a pipeline called Macless, macless.dev, that ships iOS apps to TestFlight and the App Store without owning a Mac, using GitHub Actions macOS runners for the build, sign, and upload. None of that touches the metadata side of review though. That part's still on you regardless of what builds your app. Worth knowing the difference before you start debugging the wrong thing.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>mobiledev</category>
      <category>cicd</category>
    </item>
    <item>
      <title>I ship iOS apps to the App Store without owning a Mac, using GitHub Actions' free macOS runners</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Fri, 21 Aug 2026 05:18:25 +0000</pubDate>
      <link>https://dev.to/maclessdev/i-ship-ios-apps-to-the-app-store-without-owning-a-mac-using-github-actions-free-macos-runners-b3j</link>
      <guid>https://dev.to/maclessdev/i-ship-ios-apps-to-the-app-store-without-owning-a-mac-using-github-actions-free-macos-runners-b3j</guid>
      <description>&lt;p&gt;I don't own a Mac. When I built my last iOS app (Citolex, a speed reading app with a couple of native Swift plugins), I didn't want to buy one or pay $20 to $100+/month for a rented cloud Mac just to run Xcode long enough to sign and upload a build.&lt;/p&gt;

&lt;p&gt;Turns out GitHub gives away free macOS build minutes on public repos, usually used for CI/test runs. I pointed them at the whole release pipeline instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Checkout on a free macOS runner&lt;/li&gt;
&lt;li&gt;Set bundle ID, App Group, and provisioning from GitHub Actions secrets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xcodebuild archive&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Export a signed &lt;code&gt;.ipa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Upload straight to App Store Connect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All triggered by a normal &lt;code&gt;git push&lt;/code&gt;. No Xcode GUI, no Mac, ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did it actually work
&lt;/h2&gt;

&lt;p&gt;Yes. Citolex has native Swift plugins and went through full App Store review, built entirely this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that actually trip people up
&lt;/h2&gt;

&lt;p&gt;Not the build step, that part is mechanical once it's set up. The real friction is signing: matching a distribution certificate to the right provisioning profile, keeping entitlements in sync with what's in App Store Connect, and not leaking the private key. Most of the errors I hit along the way were some flavor of "profile doesn't match this certificate."&lt;/p&gt;

&lt;h2&gt;
  
  
  Honesty notes, since these always come up
&lt;/h2&gt;

&lt;p&gt;This does not remove Apple's $99/year developer program fee, you still need that. The free build minutes are a public repo perk specifically, private repos get a much smaller free allowance. And you'll still want a physical iPhone at some point to actually test the thing you're shipping.&lt;/p&gt;

&lt;p&gt;I packaged the workflow file, signing scripts, and setup/troubleshooting docs as a one time $99 template for anyone who wants the shortcut instead of piecing it together from scratch: macless.dev. Not required to get the idea from this post, just mentioning it since people usually ask where the workflow file is.&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the signing setup specifically, that's the part most people get stuck on.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>github</category>
      <category>swift</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Set it up once. Stop thinking about it.</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Wed, 19 Aug 2026 20:38:34 +0000</pubDate>
      <link>https://dev.to/maclessdev/set-it-up-once-stop-thinking-about-it-fk0</link>
      <guid>https://dev.to/maclessdev/set-it-up-once-stop-thinking-about-it-fk0</guid>
      <description>&lt;p&gt;The whole pitch of Macless is that you shouldn't need a Mac, a rented cloud Mac, or a metered CI bill to ship an iOS or Android app — GitHub's free build minutes and a workflow file do the job Xcode used to. That part's been true since Citolex shipped on it. But there was a second, quieter kind of manual work I hadn't fully gotten rid of: generating the signing certificates and keystores in the first place, pasting the right base64 blobs into the right GitHub secrets, and then just... remembering that certificates expire, on your own, with no reminder, until a build fails the week you actually needed it to work.&lt;/p&gt;

&lt;p&gt;That's the part I went after this week. Not the pipeline — the setup and the upkeep around it. Three things, all live in the template now.&lt;/p&gt;

&lt;h2&gt;
  
  
  A wizard that runs the commands for you
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;generate_signing_secrets.sh&lt;/code&gt; already handled iOS. It now walks through Android too — generating a real keystore with &lt;code&gt;keytool&lt;/code&gt;, base64-encoding it, and telling you exactly which GitHub secret each value goes into. If you have the &lt;code&gt;gh&lt;/code&gt; CLI installed, it'll offer to push the secrets straight to your repo for you, so there's no copy-pasting eight values into a settings page by hand. And when it's done, it can hand off straight into Signing Doctor to confirm the keystore it just built is actually valid — not "the script exited zero," but an actual check that it works.&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;$ &lt;/span&gt;./scripts/generate_signing_secrets.sh
→ generates the keystore, encodes it, shows you what to &lt;span class="nb"&gt;paste&lt;/span&gt;
→ offers to push it to GitHub &lt;span class="k"&gt;for &lt;/span&gt;you via gh
→ hands off to Signing Doctor to confirm it&lt;span class="s1"&gt;'s good
no manual keytool flags, no guessing which secret name goes where.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Signing Doctor now reads Android keystores
&lt;/h2&gt;

&lt;p&gt;Signing Doctor is the free diagnostic tool I built alongside Macless — point it at your signing material and it tells you what's actually wrong, instead of you guessing from a cryptic Xcode or Gradle error. It's always checked iOS certificates and provisioning profiles. It now checks Android keystores the same way: alias, validity window, whether the password you think you have actually opens it. Same tool, same free download, one more thing it won't let slip past you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workflow that checks before you ship, not after
&lt;/h2&gt;

&lt;p&gt;The one I think matters most: &lt;code&gt;check-expiry.yml&lt;/code&gt;, a scheduled GitHub Actions workflow that runs weekly against your own signing secrets and tells you if anything's getting close to expiring — before you're mid-release and a build starts failing for a reason that has nothing to do with your code. If you've got a webhook set up, it'll ping you directly. If not, it still writes the result to the workflow's own summary, so it's there the next time you look.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why this is the part that actually saves you: a broken build from bad code is annoying but obvious — you know immediately, and you know why. An expired certificate is worse, because it looks like everything's fine until it isn't, usually at the least convenient moment. That's the failure mode this closes off. Not "the pipeline works," but "the pipeline keeps working without you having to check on it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why this is different from the alternatives
&lt;/h2&gt;

&lt;p&gt;Doing this by hand in Xcode, you get none of this — expiry is something you find out about when a build fails, and there's no diagnostic tool telling you why. Paying for a managed CI service, you might get some of it, but you're paying per build, indefinitely, for a pipeline you don't own. Macless is $19.99/month or $299 once — iOS and Android both, no build meter, and the pipeline sits in your own repo either way — and that now includes a setup that walks you through it, a free tool that tells you what's wrong when something is, and a workflow that watches your certificates so you don't have to remember to.&lt;/p&gt;

&lt;p&gt;All three are already in the template if you've bought it — pull the latest and they're there, no repurchase needed. Signing Doctor is free either way, iOS or Android, whether or not you're using the rest of Macless.&lt;/p&gt;




&lt;p&gt;The build pipeline, the setup wizard, the expiry checks, and a free Signing Doctor for whenever something looks off — all in one pipeline that lives in your repo — $19.99/month, or $299 once and nothing after. &lt;a href="https://macless.dev/pricing.html" rel="noopener noreferrer"&gt;See what's included →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>android</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
    <item>
      <title>A header scan says 16/16. Here's what it can't see.</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Tue, 18 Aug 2026 01:48:57 +0000</pubDate>
      <link>https://dev.to/maclessdev/a-header-scan-says-1616-heres-what-it-cant-see-14ab</link>
      <guid>https://dev.to/maclessdev/a-header-scan-says-1616-heres-what-it-cant-see-14ab</guid>
      <description>&lt;p&gt;A few days after &lt;a href="https://dev.to/maclessdev/how-to-build-and-ship-an-ios-app-without-a-mac-17o5"&gt;how I built and shipped an iOS app without a Mac&lt;/a&gt; went up here, a reader named Amit Feldman left a comment with an actual finding, not a vague warning: macless.dev was fast and the on-page SEO was tidy, but HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy were all missing. Six headers, zero excuses — the site's on GitHub Pages, and Pages doesn't let you set custom response headers at all. That's just how the platform works, and it's an easy thing to never notice if nobody runs the scan.&lt;/p&gt;

&lt;p&gt;The fix was straightforward once it was named: keep the site on Pages, put Cloudflare in front of the custom domain, and add the header set with a Response Header Transform Rule. HSTS is a toggle under Edge Certificates. None of it touches the deploy workflow. Amit re-scanned twice more as things landed — first the five easy headers, then a properly tight Content-Security-Policy (&lt;code&gt;default-src 'self'; script-src 'none'&lt;/code&gt;, since the page ships zero JavaScript, nothing to break) — and macless.dev sits at 16 passed, 0 warnings, 0 failures now. He's writing that before/after up as its own case study, which is a nicer way to end up getting linked than most.&lt;/p&gt;

&lt;p&gt;His follow-up point was the more useful one. A header scan reads what a browser receives — it says nothing about what a CI pipeline does with your signing certificates. Macless &lt;em&gt;is&lt;/em&gt; a signing pipeline: GitHub Actions, code-signing certificates, App Store Connect and Google Play credentials. A header scan can't tell you whether a cert is sitting in the repo, whether a workflow echoes a secret into its log, or whether a token is scoped wider than it needs to be. That's a fair distinction between two different layers, so rather than take anyone's word for either layer, I went and checked the second one directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "actually checked" means here
&lt;/h2&gt;

&lt;p&gt;Not a policy review. A search of the real git history of both &lt;code&gt;appledev&lt;/code&gt; (Citolex's source) and the Macless product template, plus a read-through of every workflow file line by line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full commit history (not just current files) searched for any certificate, private key, or keystore ever committed and later removed.&lt;/li&gt;
&lt;li&gt;Every workflow checked for secrets interpolated directly into shell commands versus passed through &lt;code&gt;env:&lt;/code&gt; (the safer pattern — GitHub can mask an env-sourced secret in logs; a value pasted straight into a &lt;code&gt;run:&lt;/code&gt; string is both a log-exposure and shell-injection risk).&lt;/li&gt;
&lt;li&gt;Checked for &lt;code&gt;set -x&lt;/code&gt;/&lt;code&gt;set -ex&lt;/code&gt;, which would dump every command — including expanded secret values — into the public build log.&lt;/li&gt;
&lt;li&gt;Checked what triggers each workflow. &lt;code&gt;pull_request_target&lt;/code&gt; combined with secrets is the classic way a fork PR exfiltrates credentials from a public repo; none of these workflows use it.&lt;/li&gt;
&lt;li&gt;Traced exactly where decoded certs and keystores get written during a build — ephemeral runner temp storage that dies with the job, or the repo working directory that could get committed or accidentally artifact-uploaded.&lt;/li&gt;
&lt;li&gt;Checked the actual GitHub Actions secret scoping this template's own docs tell you to set up (App Store Connect API key role, Play Console service account role).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What was already clean
&lt;/h2&gt;

&lt;p&gt;No certificate, key, or keystore file has ever been committed to either repo, at any point in their history. Secrets are passed through &lt;code&gt;env:&lt;/code&gt; blocks, not interpolated into shell strings. Nothing sets &lt;code&gt;-x&lt;/code&gt;. Every workflow triggers on &lt;code&gt;push&lt;/code&gt; or &lt;code&gt;workflow_dispatch&lt;/code&gt; only — nothing runs on a fork's pull request, so there's no path for an outside contributor to exfiltrate a secret through a crafted PR. Decoded cert and keystore material only ever lands in the runner's ephemeral temp directory, never the repo itself, and the artifact-upload steps grab specific screenshot files, not whole build directories. The documented credential roles (App Store Connect "App Manager," Play Console "Release manager") were already scoped down from full admin access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two real things, both fixed
&lt;/h2&gt;

&lt;p&gt;Not everything was already perfect — two genuine, if minor, gaps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. No explicit &lt;code&gt;permissions:&lt;/code&gt; block.&lt;/strong&gt; None of these workflows push commits, open PRs, or otherwise need to write back to the repo — they build, sign, and upload. Without an explicit block, &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; inherits whatever the repo or org default is, which can be broader than a workflow actually needs. Fixed by adding this to the top of every 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="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. No &lt;code&gt;.gitignore&lt;/code&gt; for signing material.&lt;/strong&gt; SIGNING.md and ANDROID.md walk you through generating a real certificate or keystore with &lt;code&gt;openssl&lt;/code&gt;/&lt;code&gt;keytool&lt;/code&gt;. Nothing in the docs stopped someone from running those commands inside their project folder, and a plain &lt;code&gt;git add .&lt;/code&gt; afterward would have committed a real private key. Fixed two ways: a &lt;code&gt;.gitignore&lt;/code&gt; now ships with the template covering &lt;code&gt;*.p12 *.key *.cer *.pem *.mobileprovision *.keystore *.jks *_base64.txt&lt;/code&gt;, and both docs now say plainly to generate this material from a scratch folder outside the repo, not inside it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you're building something similar:&lt;/strong&gt; both of these are worth checking in any CI pipeline that handles signing material, not just this one. An explicit &lt;code&gt;permissions:&lt;/code&gt; block costs three lines and closes off a class of supply-chain risk for free. A &lt;code&gt;.gitignore&lt;/code&gt; that anticipates exactly which files your setup docs are about to tell someone to generate is cheap insurance against the single most common way a real credential ends up in a public repo — someone followed the instructions correctly, in the wrong directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where this leaves things
&lt;/h2&gt;

&lt;p&gt;Both CI fixes are live now - in Citolex's own repo and in the Macless product template everyone downloads. The header layer Amit caught is fixed and independently reverified at 16/16, and he wrote the whole before/after up as &lt;a href="https://dev.to/amitfeldman/all-six-security-headers-fixed-in-a-day-the-macless-beforeafter-5343"&gt;its own case study&lt;/a&gt;. Worth being straightforward about the rest of his message too: alongside the case-study offer, he also pitched his paid audit services - a &lt;a href="https://afeldman2.gumroad.com/l/zxpluh?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=casestudy-macless" rel="noopener noreferrer"&gt;Launch-Ready Audit&lt;/a&gt; ($99) and a &lt;a href="https://afeldman2.gumroad.com/l/wmdfxb?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=casestudy-macless-deepdive" rel="noopener noreferrer"&gt;Deep Dive&lt;/a&gt; ($149) - covering exactly this CI/secrets layer. Given he'd already found three real things for free, that's not an unreasonable thing to charge for; I just happened to be able to check it myself directly, in about twenty minutes, using the repos' own git history rather than a read-only link handed to someone else. Two real, minor issues turned up, same as the header pass did. If you want the deeper, ongoing version of what he did here for free, his audits are worth a look. Thanks are owed either way: the header catch was real, the nudge to check the layer underneath it was a good one, and the case study is a fair trade for both.&lt;/p&gt;




&lt;p&gt;SIGNING.md and ANDROID.md now warn you before you generate anything, every workflow ships with a scoped-down &lt;code&gt;permissions:&lt;/code&gt; block, and the &lt;code&gt;.gitignore&lt;/code&gt; is there as a backstop either way — all part of &lt;a href="https://macless.dev" rel="noopener noreferrer"&gt;Macless&lt;/a&gt;, a one-time template for shipping an iOS (and now Android) app through GitHub Actions without a Mac.&lt;/p&gt;

</description>
      <category>security</category>
      <category>github</category>
      <category>cicd</category>
      <category>ios</category>
    </item>
    <item>
      <title>The actual cost of shipping an iOS app in 2026</title>
      <dc:creator>Jackson</dc:creator>
      <pubDate>Sun, 16 Aug 2026 02:55:04 +0000</pubDate>
      <link>https://dev.to/maclessdev/the-actual-cost-of-shipping-an-ios-app-in-2026-4242</link>
      <guid>https://dev.to/maclessdev/the-actual-cost-of-shipping-an-ios-app-in-2026-4242</guid>
      <description>&lt;p&gt;"How much does it cost to put an app on the App Store" gets answered inconsistently online because most answers either only count Apple's fee, or only count hardware, or quietly assume you're renting expensive cloud infrastructure you don't actually need. Here's every cost, split into what's mandatory and what's a choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mandatory: Apple Developer Program — $99/year
&lt;/h3&gt;

&lt;p&gt;This is the one cost nobody can avoid. To submit any app to the App Store — free or paid, one app or fifty — you need an active Apple Developer Program membership, which is $99/year, billed annually, direct to Apple. There's no one-time version and no way around it.&lt;/p&gt;

&lt;p&gt;(There is a free-tier Apple ID for personal on-device testing without paying this, but it doesn't let you submit to TestFlight external testers or the App Store — for an actual public release, the $99/year membership is required.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Required, but where you have genuine choices: building and signing
&lt;/h3&gt;

&lt;p&gt;To submit a build, something has to run Xcode's command-line signing and archive tools — that part isn't optional. Where you have a choice is what runs it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Recurring?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Buy a Mac&lt;/td&gt;
&lt;td&gt;~$799+ (Mac mini, entry price)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rent a cloud Mac&lt;/td&gt;
&lt;td&gt;~$20–100+/month&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions, public repo&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions, private repo&lt;/td&gt;
&lt;td&gt;$0 up to a monthly allowance, then per-minute&lt;/td&gt;
&lt;td&gt;Only if you exceed the free allowance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The short version on that last row: on a public repo, this line item can legitimately be $0, indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional or one-time: the things people assume cost more than they do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;App Store screenshots and marketing assets. You can generate these yourself for free — from the Simulator or a physical device — no paid tooling required.&lt;/li&gt;
&lt;li&gt;A physical iPhone for testing. Not strictly required to submit, but you'll want one to sanity-check the finished app before release.&lt;/li&gt;
&lt;li&gt;TestFlight itself. Free, included in the $99/year membership.&lt;/li&gt;
&lt;li&gt;App Store listing itself. Free — no fee to list an app beyond the membership.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adding it up
&lt;/h3&gt;

&lt;p&gt;For someone shipping a side project on a public repo, using GitHub Actions instead of a Mac or a cloud rental:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apple Developer Program ....... $99/year  (mandatory)
Build &amp;amp; sign (public repo CI) ..... $0    (GitHub Actions)
Screenshots / assets .............. $0    (self-generated)
TestFlight / listing .............. $0    (included)
-----------------------------------------
Total to actually ship ......... $99/year
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that to the same app built with a purchased Mac (~$898 first year, then $99/year after) or a rented cloud Mac (~$339–1,299 first year, then that same range every year after). The gap is entirely in the "how do I run Xcode" line item.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth flagging plainly:&lt;/strong&gt; the $0 build-cost row above is specific to public repositories. Keeping your code private doesn't change how the pipeline works or expose anything extra — it just switches you from unlimited free minutes to a metered monthly allowance. Still usually cheaper than a Mac or cloud rental, just not literally free.&lt;/p&gt;

&lt;p&gt;I built the pipeline behind this shipping &lt;a href="https://citolex.com" rel="noopener noreferrer"&gt;Citolex&lt;/a&gt; specifically to avoid a recurring Mac-rental bill on top of Apple's own fee. It's packaged at &lt;a href="https://macless.dev" rel="noopener noreferrer"&gt;macless.dev&lt;/a&gt; — $19.99/month, or $299 once and nothing after. Either way there's no build meter, and the pipeline lives in your own repo, so cancelling doesn't stop your builds.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>indiehackers</category>
      <category>github</category>
      <category>mobiledev</category>
    </item>
  </channel>
</rss>
