Skip to main content

iOS CI/CD Guide

This document explains how iOS builds work for Eli Health.


Overview

iOS builds are automated using Fastlane and GitHub Actions. When code is pushed to a branch, a build is automatically triggered, uploaded to TestFlight, and made available for testing.


Environments

BranchApp NameBundle IDBuild ConfigBackend
development2Eli Dev 2health.eli.Eli.dev2Dev2.Releasedev.eli-app.com
developmentEli Devhealth.eli.Eli.devDev.Releasedev.eli-app.com
stagingEli Stagehealth.eli.Eli.stagingStaging.Releasestaging.eli-app.com
mainElihealth.eli.EliProd.Releaseapp.eli-app.com

Each environment has its own:

  • Bundle identifier (appears in App Store Connect as separate apps)
  • Display name (shown on device home screen)
  • Associated domains (for deep linking)
  • Firebase/Google configuration

How Builds Are Triggered

Builds trigger automatically on push:

WorkflowTriggerFastlane Lane
ios-dev2-build.ymlPush to development2dev2
ios-dev-build.ymlPush to developmentdev
ios-staging-build.ymlPush to stagingstaging
ios-prod-build.ymlPush to mainprod

You can also manually trigger builds from GitHub Actions → Select workflow → "Run workflow".


Development2 Branch

The development2 branch is a secondary development environment for testing large features before they're merged to development.

When to Use development2

Use development2 for:

  • Large refactors that need extended testing
  • Features that may take several iterations to stabilize
  • Changes that could disrupt regular development flow

How It Works

  1. Same backend as development - Both development and development2 point to dev.eli-app.com
  2. Separate TestFlight app - "Eli Dev 2" appears as a separate app on TestFlight
  3. Isolated testing - QA can test new features without affecting the main dev build
  4. Merge when ready - Once stable, merge development2development

Workflow Example

# Start a large feature
git checkout development2
git checkout -b EXM-123-big-feature

# Work on feature, create PR targeting development2
# After PR is merged, development2 builds "Eli Dev 2"
# QA tests on Eli Dev 2

# When feature is stable, merge development2 into development
git checkout development
git merge development2
git push origin development

Build Process

Key Steps

  1. Configure Environment - Runs npm run configure:[dev|stg|prod] to set bundle ID, display name, and associated domains

  2. Fetch Certificates - Uses Match to download signing certificates from eli-health/eli-fastlane-match repository

  3. Build - Compiles the app with the correct configuration using Xcode 16

  4. Upload - Sends the IPA to TestFlight using App Store Connect API


Certificates & Signing

Match

Certificates and provisioning profiles are stored encrypted in a separate Git repository:

  • Repository: git@github.com:eli-health/eli-fastlane-match.git
  • Encryption: Files are encrypted with MATCH_PASSWORD

Certificate Types

TypeUsed ForEnvironments
appstoreApp Store & TestFlight distributionAll
developmentLocal developmentAll

Creating New Certificates

Run locally (not in CI):

cd eli-app/ios
MATCH_PASSWORD='xxx' \
APP_STORE_CONNECT_API_KEY_ID='xxx' \
APP_STORE_CONNECT_ISSUER_ID='xxx' \
bundle exec fastlane ios create_certs

GitHub Secrets

These secrets are configured in eli-health/eli-app repository settings:

SecretDescription
MATCH_PASSWORDDecrypts certificates in match repository
MATCH_GIT_PRIVATE_KEYSSH key to access match repository
APP_STORE_CONNECT_API_KEYContents of AuthKey.p8 file
APP_STORE_CONNECT_API_KEY_IDAPI Key ID from App Store Connect
APP_STORE_CONNECT_ISSUER_IDIssuer ID from App Store Connect

App Store Connect API Key

The API key allows automated authentication without 2FA:

  1. Go to App Store Connect → Users and Access → Integrations
  2. Generate API Key with "App Manager" role
  3. Download the .p8 file (one-time download)
  4. Note the Key ID and Issuer ID

Build Numbers

Build numbers auto-increment based on the highest of:

  • Latest build on TestFlight for that app
  • Current build number in Xcode project

This ensures unique build numbers even when multiple builds run concurrently.


File Structure

eli-app/
├── ios/
│ ├── fastlane/
│ │ ├── Fastfile # Build lanes
│ │ ├── Matchfile # Certificate config
│ │ └── Appfile # App metadata
│ ├── Gemfile # Ruby dependencies
│ └── Podfile # CocoaPods dependencies
├── .github/workflows/
│ ├── ios-build.yml # Reusable workflow
│ ├── ios-dev2-build.yml # Dev2 trigger (development2 branch)
│ ├── ios-dev-build.yml # Dev trigger
│ ├── ios-staging-build.yml # Staging trigger
│ └── ios-prod-build.yml # Production trigger
└── scripts/
└── configure-environment.js # Sets bundle ID, display name, etc.

Common Commands

Check Build Status

# List recent builds
gh run list --repo eli-health/eli-app --workflow "iOS Dev Build"
gh run list --repo eli-health/eli-app --workflow "iOS Staging Build"
gh run list --repo eli-health/eli-app --workflow "iOS Production Build"

# View specific run
gh run view <RUN_ID> --repo eli-health/eli-app

# View failed logs
gh run view <RUN_ID> --repo eli-health/eli-app --log-failed

Sync Certificates Locally

cd eli-app/ios
MATCH_PASSWORD='xxx' bundle exec fastlane match appstore --readonly

Build Locally (No Upload)

cd eli-app/ios
bundle exec fastlane ios build_dev
bundle exec fastlane ios build_staging
bundle exec fastlane ios build_prod

Troubleshooting

"No matching provisioning profiles found"

Run create_certs locally to create the missing profile:

MATCH_PASSWORD='xxx' bundle exec fastlane ios create_certs

"Maximum number of certificates generated"

Apple limits distribution certificates to 3 per account. Revoke unused ones at Apple Developer Portal.

Build takes too long

Typical build times:

  • With warm cache: ~20-25 minutes
  • Cold cache: ~25-30 minutes

The CocoaPods cache significantly reduces build time.

"must be built with iOS 18 SDK"

The workflow automatically selects Xcode 16. If this error appears, check that the Xcode selection step is present in the workflow.


Quick Reference

ActionCommand
Trigger dev2 buildPush to development2 branch
Trigger dev buildPush to development branch
Trigger staging buildPush to staging branch
Trigger production buildPush to main branch
Create certificatesfastlane ios create_certs (local only)
Sync certificatesfastlane match appstore --readonly
Build locallyfastlane ios build_[dev2|dev|staging|prod]