A production-ready iOS CI/CD pipeline demonstrating automated testing and TestFlight deployment using GitHub Actions and Fastlane.
- Comment-Triggered Builds - Deploy with
/buildcomments on PRs - Automated Testing - Unit tests run on every PR to develop
- TestFlight Integration - Direct uploads with auto-distribution to internal testers
- Smart Build Management - Automatic build number increment with conflict resolution
- Flexible Code Signing - Supports both Match (team) and manual certificate workflows
- Real-Time Status - Emoji reactions show build progress (👀 → 🚀/😞)
- Export Compliance - Automatic handling, no manual submission needed
- CI/CD: GitHub Actions
- Build Automation: Fastlane 2.229.0
- iOS: Xcode 26.x (iOS 26 SDK), iOS 16.0+ deployment target
- Language: Swift 6.0, Ruby 3.3
- Trigger: PRs to
developbranch + manual dispatch - Actions: Checkout → Setup Xcode → Install dependencies → Run tests
- Job Name:
test(referenced by build workflow)
- Trigger:
/buildcomments on PRs - Validations: Branch check (develop/feature/*) + test status verification
- Actions: Build → Sign → Upload to TestFlight
- Status: Real-time emoji reactions on comments
PR Comment: /build
↓
👀 Building (emoji reaction)
↓
Branch Validation (develop/feature/* only)
↓
Test Status Check (must not be failed)
↓
Environment Setup (Xcode, Ruby, Fastlane)
↓
Code Signing (certificates + provisioning profiles)
↓
Build Archive (Release configuration)
↓
TestFlight Upload (with changelog)
↓
🚀 Success / 😞 Failure (emoji reaction)
- Active Apple Developer Program membership
- App Store Connect access (Developer role minimum)
- GitHub repository with Actions enabled
Add to Settings → Secrets → Actions:
# App Store Connect API
API_KEY_ID=XXXXXXXXXX # App Store Connect API Key ID
API_ISSUER_ID=xxxxxxxx-xxxx # App Store Connect Issuer ID
API_KEY_BASE64=LS0tLS1CRUd... # Base64 encoded .p8 file
# Apple Developer Account
DEVELOPMENT_TEAM=XXXXXXXXXX # Apple Developer Team ID
FASTLANE_USERNAME=you@email.com # Apple ID email
# Code Signing
DISTRIBUTION_CERTIFICATE=MII... # Base64 encoded .p12 certificate
DISTRIBUTION_PASSWORD=password # Certificate password
APP_STORE_PROFILE_BASE64=MII... # Base64 encoded provisioning profile- Visit App Store Connect → Users and Access → Keys
- Create new key with Developer role
- Download
.p8file - Convert to Base64:
base64 -i AuthKey_XXXXXXXXXX.p8 | pbcopy - Add to GitHub secrets as
API_KEY_BASE64
- Create PR to
developbranch (tests run automatically) - Comment
/buildon PR (triggers TestFlight deployment) - Monitor emoji reactions: 👀 (building) → 🚀 (success) / 😞 (failed)
├── .github/workflows/
│ ├── testing_workflow.yaml # Automated testing on PRs
│ └── build-on-comment.yml # Comment-triggered builds
├── fastlane/
│ ├── Fastfile # Build automation logic
│ ├── Appfile # App configuration
│ ├── Matchfile # Certificate management (optional)
│ └── template.env # Environment variables template
├── CD starter project/ # iOS app source code
├── CD starter projectTests/ # Unit tests
├── CD-starter-project-Info.plist # Export compliance configuration
├── Gemfile # Ruby dependencies
└── .gitignore # Comprehensive exclusions
fastlane test- Run unit tests locallyfastlane build_and_upload- Build and upload to TestFlight
- Dual Certificate Support: Match (team sharing) or manual (individual)
- Build Number Management: Fetches latest from TestFlight and increments
- Error Handling: Graceful handling of build number conflicts
- CI Optimization: Temporary keychain management for secure builds
# Clone repository
git clone <your-repo-url>
cd <project-directory>
# Install dependencies
bundle install
# Run tests
bundle exec fastlane test- Open
CD starter project.xcodeproj - Build and run on simulator/device
- Tests located in
CD starter projectTests/
- ✅ All secrets encrypted in GitHub
- ✅ Temporary file cleanup after builds
- ✅ Proper keychain management in CI
- ✅ No sensitive data in logs
- ✅ Branch protection with test validation
- ✅ Comprehensive .gitignore for iOS projects
- Only
developandfeature/*branches can trigger builds - Tests must not be in failed state to proceed with builds
- Build numbers are automatically incremented from TestFlight
- Internal testers receive builds automatically; external testers require manual distribution