I used to dread shipping builds.
Not the coding part — that was fine. The deployment part. Open Xcode. Wait for indexing. Archive. Wait. Upload to App Store Connect. Wait. Realize I forgot to bump the build number. Archive again. Wait again. The whole ritual took 20-30 minutes of my attention, and I couldn’t do anything else while Xcode held my machine hostage.
So I shipped less often. Weekly builds became biweekly. Biweekly became “when I feel like it.” Each deployment got scarier because the diff was bigger. More changes meant more risk. More risk meant more dread. A vicious cycle.
Then I spent two hours setting up a CI/CD pipeline. Now my deployment process is git push origin main. I walk away, make coffee, and by the time I’m back, there’s a new build in TestFlight. No Xcode archiving. No manual uploads. No attention consumed.
Here’s the thing most CI/CD for indie developers guides won’t tell you: the real value isn’t the automation. It’s what the automation does to your behavior.
Why Solo Developers Need CI/CD More Than Teams Do
This sounds backwards, so let me explain.
A team has layers of protection: code review catches bugs before merge. QA catches issues before release. A release manager coordinates the deployment. If something goes wrong, there are people to help debug.
A solo developer has none of that. You are the coder, the reviewer, the QA, and the release manager. Every manual step in your deployment process is a step where you — tired, distracted, context-switching — can make a mistake. And there’s no one to catch it.
CI/CD for indie developers replaces those missing team members with automation:
| Team Role | CI/CD Equivalent |
|---|---|
| Code reviewer | Automated test suite (catches regressions) |
| QA engineer | Build verification (does it compile? do tests pass?) |
| Release manager | Automated deployment (correct build number, signed, uploaded) |
| Ops engineer | Pipeline monitoring (notifications on failure) |
The argument that “CI/CD is overkill for solo projects” is exactly wrong. It’s overkill for large teams who already have human safety nets. For solo developers, it is the safety net.
The $0 Stack: GitHub Actions + Fastlane
Here’s what I use. Total monthly cost: $0.
GitHub Actions provides the CI/CD runner — the machine that executes your pipeline. The free tier includes 2,000 minutes/month. macOS runners use a 10x multiplier, so that’s effectively 200 macOS minutes. A typical iOS build takes 8-12 minutes. That’s 16-25 builds per month for free.
Fastlane handles the iOS-specific automation — building, signing, versioning, and uploading to TestFlight. It’s open source, actively maintained (last updated March 2026), and has 41,000+ GitHub stars for a reason: it works.
⚠️ Watch out: Why not Xcode Cloud? It’s Apple’s own CI/CD, but it costs $14.99/month for 25 compute hours after the free tier. For an indie developer, GitHub Actions’ free tier is usually sufficient, and Fastlane gives you more control over the pipeline.
Setting Up Fastlane (One-Time, 30 Minutes)
First, install Fastlane:
# Using Homebrew (recommended) brew install fastlane # Navigate to your iOS project cd ~/Projects/MyApp # Initialize Fastlane fastlane init # Choose "Manual setup" when prompted
This creates a fastlane/ directory with a Fastfile. Here’s a practical Fastfile for an indie iOS developer:
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Run tests"
lane :test do
run_tests(
scheme: "MyApp",
devices: ["iPhone 16"],
clean: true
)
end
desc "Build and upload to TestFlight"
lane :beta do
# Increment build number using the latest TestFlight build number
increment_build_number(
build_number: latest_testflight_build_number + 1
)
# Build the app
build_app(
scheme: "MyApp",
export_method: "app-store",
clean: true
)
# Upload to TestFlight
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
end
For App Store Connect authentication, use an API key (not username/password — Apple requires 2FA which breaks CI):
# Generate an API key at https://appstoreconnect.apple.com/access/integrations/api # Download the .p8 file # You'll need: KEY_ID, ISSUER_ID, and the .p8 key content
Create a fastlane/Appfile:
# fastlane/Appfile
app_identifier("com.yourname.myapp")
Test locally first:
fastlane test # Run tests fastlane beta # Build + upload to TestFlight
If this works locally, you’re 80% done. The remaining 20% is telling GitHub Actions to run the same thing.
The GitHub Actions Workflow (The Actual Pipeline)
Create .github/workflows/deploy.yml in your repository:
name: Build and Deploy to TestFlight
on:
push:
branches: [main]
jobs:
build:
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Install Fastlane
run: gem install fastlane
- name: Install App Store Connect API Key
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
run: |
mkdir -p ~/private_keys
echo "$ASC_KEY_CONTENT" > ~/private_keys/AuthKey_${ASC_KEY_ID}.p8
- name: Run tests
run: fastlane test
- name: Build and upload to TestFlight
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_PATH: ~/private_keys/AuthKey_${{ secrets.ASC_KEY_ID }}.p8
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
run: fastlane beta
Setting Up GitHub Secrets
Go to your repo → Settings → Secrets and variables → Actions, and add:
| Secret | Where to get it |
|---|---|
ASC_KEY_ID | App Store Connect → Users and Access → Integrations → Keys |
ASC_ISSUER_ID | Same page, shown at the top |
ASC_KEY_CONTENT | The contents of the downloaded .p8 file |
MATCH_PASSWORD | The password for your Fastlane Match encryption (if using Match) |
Pro tip: For code signing on CI, I recommend Fastlane Match. It stores your certificates and provisioning profiles in a private Git repo, encrypted. The CI runner checks them out during the build. No more “missing provisioning profile” errors.
# Add to your Fastfile beta lane, before build_app: setup_ci # creates a temporary keychain for CI match(type: "appstore", readonly: true)
Obsidian for Developers: Why I Chose It Over Notion and Never Looked Back
Here’s What Most People Miss About CI/CD

Let me share the non-obvious benefit I discovered after a month of using this pipeline.
The pipeline changed how I write code.
When I deployed manually, I’d batch changes into big releases. “I’ll deploy once I finish this feature AND fix those three bugs AND update the dependencies.” Big deployments. High risk. High stress.
With automated CI/CD, every push to main triggers a build. This naturally pushed me toward:
Smaller commits. Each commit needs to pass tests and build successfully. I stopped cramming three features into one commit.
Better tests. When tests run on every push, you notice when coverage is thin. That screen you never tested? It’ll break in CI eventually, and you’ll add a test for it.
Clean main branch. I started using feature branches and merging only when ready. Not because a process document told me to, but because the pipeline punished sloppy merges with failed builds.
Organized certificates. When Fastlane Match manages your signing, you never have the “expired provisioning profile on deploy day” panic again.
The pipeline isn’t just automating deployment. It’s a forcing function for solo engineering discipline. It’s the structure that a team environment provides naturally but a solo developer has to build intentionally.
What Doesn’t Work (Honest Assessment)
macOS runner minutes burn fast. The 10x multiplier on GitHub Actions means your 2,000 free minutes are really 200 macOS minutes. If you’re shipping multiple apps or running long test suites, you’ll hit the limit. Options: optimize test parallelism, use a self-hosted Mac Mini runner, or pay for more minutes ($0.08/min for macOS).
Initial signing setup is painful. Getting certificates, provisioning profiles, and App Store Connect API keys configured correctly is the hardest part. Budget 1-2 hours for the first time. Fastlane Match helps enormously, but the learning curve is real.
Xcode version mismatches. GitHub’s macOS runners update Xcode on their own schedule. If your project needs a specific Xcode version, add the maxim-lobanov/setup-xcode action to your workflow. This occasionally breaks after Apple releases a new Xcode.
Not everything should be automated. I only auto-deploy to TestFlight, not the App Store. The App Store submission includes screenshots, descriptions, and review notes that I want to control manually. The pipeline ends at TestFlight; the final release is intentional.
Frequently Asked Questions
How much does this CI/CD setup cost for a solo developer?
$0 for most indie developers. GitHub Actions’ free tier provides 2,000 minutes/month (200 macOS-equivalent minutes). Fastlane is open source. You’ll only pay if you exceed the free tier or want Xcode Cloud ($14.99/month).
Can I use this for SwiftUI apps targeting iOS 26?
Yes. The pipeline is Xcode-version agnostic — just make sure the GitHub Actions runner has the Xcode version you need. Use the setup-xcode action to pin a specific version.
Do I really need automated tests for a solo project?
Yes. You are the only safety net. Even a minimal test suite (model logic, API parsing, critical user flows) catches regressions that manual testing misses. Start with 10 tests. Add more as you encounter bugs.
What about Android or cross-platform apps?
Fastlane supports Android too, with lanes for gradle builds and Google Play uploads. GitHub Actions runs Linux and Windows runners as well. The same principles apply; only the build tools change.
The goal of CI/CD isn’t faster builds. It’s boring deployments. When shipping a new build is as routine and unremarkable as pushing a commit, you’ve won. Your attention goes back to what matters: building the product. Set up the pipeline once, and every deployment after that is free — in both time and mental energy.



