1. Khalid Alaoui, iOS developer
  2. Release pipeline
Case study

Hundreds of apps, one command

iOS CI/CD for hundreds of apps: Fastlane, GitHub Actions on a self-hosted Mac mini, Match code signing and the App Store Connect API, so a release to every app is one command.

0 → 333merchant configurations on template 3.2.04 between 31 Jul and 31 Aug 2026
290 / 290live apps now on the same release
555 / 557commits since the pipeline landed (27 Mar 2026) are mine

One command.
Every app.

zsh: branded-apps

0of live apps released

An illustration of one run, drawn with the real list of live apps from merchants.json. The command is the real one.

The problem

Branding was injected into the project at build time by a git hook and a target-generator package, and there was no Fastlane or CI release pipeline.

With hundreds of apps, every template update meant a lot of manual work.

What I did

  1. 01
    Made merchants.json the single source of truth and generated one xcconfig per merchant from it.
  2. 02
    Wrote switch.sh to stage icons, splash screens and Firebase config into gitignored paths, and removed the generator package.
  3. 03
    Built Fastlane and GitHub Actions on a self-hosted Mac mini, with Match signing, build numbers read from App Store Connect and version-based detection of outdated apps.
  4. 04
    Parallel builds, each in its own git worktree. A pre-commit hook blocks leaked keys and webhooks.

What it looks like to use

# work on one restaurant locally
$ ./scripts/switch.sh bagel

# what would a release touch?
$ ./scripts/update_merchants.sh --dry-run

# ship every outdated app, four at a time
$ ./scripts/update_merchants.sh --concurrency 4

# a brand-new client, registered in App Store Connect
$ ./scripts/create_merchant.sh newslug

Need an iOS
developer?

[email protected] →

Freelance projects, or a full-time role, remote or with relocation. Résumé (PDF)