Awesome-omni-skill ios-ci-cd
Set up CI/CD pipelines for iOS apps using GitHub Actions, Fastlane, and Xcode Cloud. Use when configuring automated builds, testing, code signing, TestFlight deployment, or App Store release automation. Part of the idea-to-App-Store workflow before publishing.
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/devops/ios-ci-cd" ~/.claude/skills/diegosouzapw-awesome-omni-skill-ios-ci-cd && rm -rf "$T"
manifest:
skills/devops/ios-ci-cd/SKILL.mdsource content
iOS CI/CD
Automate building, testing, and deploying iOS applications.
CI/CD Overview
Push → Build → Test → Sign → Deploy → Notify ↓ ↓ ↓ ↓ SwiftLint Unit Match TestFlight UI App Store
GitHub Actions
Basic Workflow
# .github/workflows/ios.yml name: iOS CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: build-and-test: runs-on: macos-14 # Latest macOS with Xcode steps: - uses: actions/checkout@v4 - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode_15.2.app - name: Show Xcode version run: xcodebuild -version - name: Install dependencies run: | brew install swiftlint - name: Run SwiftLint run: swiftlint --strict - name: Build run: | xcodebuild build \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -destination 'platform=iOS Simulator,name=iPhone 15' \ -configuration Debug \ CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO - name: Run Tests run: | xcodebuild test \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -destination 'platform=iOS Simulator,name=iPhone 15' \ -enableCodeCoverage YES \ -resultBundlePath TestResults - name: Upload Test Results uses: actions/upload-artifact@v4 if: always() with: name: test-results path: TestResults
TestFlight Deployment
# .github/workflows/deploy.yml name: Deploy to TestFlight on: push: tags: - 'v*' jobs: deploy: runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode_15.2.app - name: Install Fastlane run: gem install fastlane - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' bundler-cache: true - name: Install Certificates env: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} run: fastlane match appstore --readonly - name: Build and Upload env: APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} run: fastlane beta
Fastlane Configuration
Setup
# Install Fastlane gem install fastlane # Initialize in project cd /path/to/project fastlane init # This creates: # fastlane/Fastfile # fastlane/Appfile
Appfile
# fastlane/Appfile app_identifier("com.example.myapp") apple_id("developer@example.com") team_id("XXXXXXXXXX") # App Store Connect API Key (preferred over password) # app_store_connect_api_key( # key_id: "D383SF739", # issuer_id: "6053b7fe-68a8-4acb-89be-165aa6465141", # key_filepath: "./AuthKey_D383SF739.p8" # )
Fastfile
# fastlane/Fastfile default_platform(:ios) platform :ios do # Run before all lanes before_all do ensure_git_status_clean end # MARK: - Testing desc "Run all tests" lane :test do scan( workspace: "MyApp.xcworkspace", scheme: "MyApp", device: "iPhone 15", code_coverage: true, output_directory: "./fastlane/test_output" ) end # MARK: - Beta desc "Push a new beta build to TestFlight" lane :beta do # Increment build number increment_build_number( build_number: Time.now.strftime("%Y%m%d%H%M") ) # Sync certificates match(type: "appstore", readonly: true) # Build build_app( workspace: "MyApp.xcworkspace", scheme: "MyApp", configuration: "Release", export_method: "app-store" ) # Upload to TestFlight upload_to_testflight( skip_waiting_for_build_processing: true ) # Notify slack( message: "New beta build uploaded to TestFlight! 🚀", success: true ) end # MARK: - Release desc "Push a new release to App Store" lane :release do # Run tests first test # Increment version increment_version_number( bump_type: "patch" # major, minor, patch ) # Build and upload match(type: "appstore", readonly: true) build_app( workspace: "MyApp.xcworkspace", scheme: "MyApp", configuration: "Release", export_method: "app-store" ) # Upload to App Store upload_to_app_store( submit_for_review: false, automatic_release: false, skip_screenshots: true, skip_metadata: false ) # Create git tag add_git_tag(tag: "v#{get_version_number}") push_to_git_remote end # MARK: - Code Signing desc "Sync certificates and profiles" lane :sync_signing do match(type: "development") match(type: "appstore") end # MARK: - Screenshots desc "Capture screenshots" lane :screenshots do capture_screenshots frame_screenshots(silver: true) end # Error handling error do |lane, exception| slack( message: "Build failed: #{exception.message}", success: false ) end end
Code Signing with Match
Setup Match
# Initialize match fastlane match init # Create certificates and profiles fastlane match development fastlane match appstore # On CI, use readonly fastlane match appstore --readonly
Matchfile
# fastlane/Matchfile git_url("git@github.com:yourcompany/certificates.git") storage_mode("git") type("appstore") # development, adhoc, appstore app_identifier(["com.example.myapp"]) username("developer@example.com") # For CI # git_basic_authorization(ENV["MATCH_GIT_BASIC_AUTHORIZATION"])
CI Secrets Required
# GitHub Secrets MATCH_PASSWORD # Encryption password for certificates MATCH_GIT_URL # Git repo URL for certificates MATCH_GIT_BASIC_AUTHORIZATION # base64(username:token) # App Store Connect API Key APP_STORE_CONNECT_KEY_ID APP_STORE_CONNECT_ISSUER_ID APP_STORE_CONNECT_KEY # .p8 key content
Xcode Cloud
Workflow Configuration
# ci_scripts/ci_post_clone.sh #!/bin/sh # Install dependencies brew install swiftlint # Install Swift packages xcodebuild -resolvePackageDependencies
Custom Scripts
# ci_scripts/ci_pre_xcodebuild.sh #!/bin/sh # Run SwiftLint swiftlint --strict # Environment-specific configuration echo "Building for environment: $CI_WORKFLOW"
# ci_scripts/ci_post_xcodebuild.sh #!/bin/sh # Post-build actions if [ "$CI_XCODEBUILD_EXIT_CODE" -eq 0 ]; then echo "Build succeeded!" # Send success notification else echo "Build failed!" # Send failure notification fi
Xcode Cloud Workflow Settings
Workflow: Beta Releases ├── Start Conditions: │ └── Tag: v* ├── Environment: │ ├── Xcode: Latest Release │ └── macOS: Latest ├── Actions: │ ├── Build: iOS │ ├── Test: iOS │ └── Archive: iOS └── Post-Actions: └── Deploy to TestFlight
Build Matrix
Multi-Configuration Testing
# .github/workflows/matrix.yml jobs: test: runs-on: macos-14 strategy: matrix: xcode: ['15.0', '15.2'] ios: ['17.0', '17.2'] device: ['iPhone 15', 'iPad Pro (12.9-inch) (6th generation)'] steps: - uses: actions/checkout@v4 - name: Select Xcode ${{ matrix.xcode }} run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - name: Test run: | xcodebuild test \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -destination 'platform=iOS Simulator,name=${{ matrix.device }},OS=${{ matrix.ios }}'
Useful Actions
Caching
- name: Cache Swift packages uses: actions/cache@v4 with: path: .build key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: | ${{ runner.os }}-spm- - name: Cache Ruby gems uses: actions/cache@v4 with: path: vendor/bundle key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
Notifications
- name: Slack Notification uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} fields: repo,message,commit,author,action,eventName,ref,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} if: always()
Release Checklist
Pre-Release
- All tests passing
- Version number incremented
- Build number unique
- Release notes prepared
- Screenshots updated (if needed)
Deploy Commands
# Local deployment fastlane beta # TestFlight fastlane release # App Store # Specific version fastlane beta version:1.2.0 # Skip certain steps fastlane beta skip_tests:true
Post-Release
- Tag created and pushed
- Release notes published
- Team notified
- Monitor crash reports
- Monitor reviews
Troubleshooting
Common Issues
| Issue | Solution |
|---|---|
| Code signing failed | Run locally first |
| Build timeout | Increase GitHub Actions timeout |
| Simulator not found | Check Xcode version and device name |
| Upload failed | Verify API key permissions |
Debugging Tips
# Verbose output fastlane beta --verbose # Print environment fastlane action env # Validate signing fastlane run validate_provisioning_profile
Resources
See assets/github-actions/ for workflow templates. See assets/fastlane/ for Fastfile templates.